Romania, Bulgaria and Hungary marketplace data behind one parameter
The eMAG API returns the dominant marketplace of Romania, plus the same company's Bulgarian and Hungarian storefronts, as clean JSON behind a single market parameter.
4 active endpoints, on 1 and 2 credit tiers.
- POST/emag/v1/search
- POST/emag/v1/category
- POST/emag/v1/product/detail
- POST/emag/v1/product/offers
What eMAG endpoints does ReefAPI ship?
4 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.
eMAG API
4 of 4 endpoints, ready to run
Every merchant selling the same product on eMAG: the buybox offer with its seller rating, stock, delivery estimate and warranty, plus each competing marketplace seller with that seller's price.
{ "ok": true, "meta": { "api": "emag", "endpoint": "product/offers", "mode": "live", "latency_ms": 2068.9, "record_count": 7, "cache_hit": false }, "data": { "offers": [ { "offer_id": 197175224, "is_buybox": true, "price": 709.99, "currency": "RON", "lowest_price_30_days": null, "recommended_retail_price": null, "discount_percent": null, "promotion_note": null, "seller": { "id": 1, "name": "eMAG", "slug": "emag", "url": "https://www.emag.ro/emag/1/v", "type": "emag", "rating": 4.8, "rating_count": null, "positive_feedback_percent": 95, "fulfilled_by_emag": true }, "in_stock": true, "availability": "in_stock", "delivery_days_estimate": 0, "free_delivery": false, "fulfilled_by_emag": false, "genius_eligible": true, "is_resealed": false, "is_used": false, "has_warranty": true, "warranty_months": 24, "return_period_days": null, "unit": "buc" }, { "offer_id": 199632910, "is_buybox": false, "price": 789.99, "currency": "RON", "lowest_price_30_days": 808.99, "recommended_retail_price": null, "discount_percent": 2.3, "promotion_note": "Promoție valabilă de la data de 25-08-2026, în limita stocului disponibil.", "seller": { "id": 199, "name": "Flanco", "slug": "flanco", "url": "https://www.emag.ro/flanco/199/v", "type": "marketplace", "rating": 4.7, "rating_count": null, "positive_feedback_percent": 92, "fulfilled_by_emag": true }, "in_stock": true, "availability": "in_stock", "delivery_days_estimate": 0, "free_delivery": false, "fulfilled_by_emag": false, "genius_eligible": true, "is_resealed": true, "is_used": false, "has_warranty": true, "warranty_months": 24, "return_period_days": 14, "unit": "buc" }, { "offer_id": 261345275, "is_buybox": false, "price": 822, "currency": "RON", "lowest_price_30_days": null, "recommended_retail_price": null, "discount_percent": null, "promotion_note": null, "seller": { "id": null, "name": "Dwyn Shop", "slug": null, "url": null, "type": null, "rating": null, "positive_feedback_percent": null, "fulfilled_by_emag": null }, "in_stock": null, "availability": null, "delivery_days_estimate": null, "free_delivery": null, "fulfilled_by_emag": null, "genius_eligible": null, "is_resealed": null, "is_used": null, "has_warranty": null, "warranty_months": null, "return_period_days": null, "unit": null } ], "offer_count": 7, "min_price": 709.99, "max_price": 940.71, "currency": "RON", "product": { "product_code": "DBFC7LYBM", "title": "Casti Apple AirPods 4", "url": "https://www.emag.ro/casti-apple-airpods-4-mxp63zm-a/pd/DBFC7LYBM/" }, "market": "ro" } }
How the eMAG API works
eMAG is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.
No OAuth app, no request signing, no per-site account. One key covers all 188 engines.
Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.
Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.
One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.
Find your product in a market, then see every seller competing on it
eMAG is a marketplace, so the interesting number is not the price - it is the spread across the sellers fighting over the same listing. That is one extra call, and it is the cheapest action on the engine.
{"query": "casti bluetooth", "market": "ro", "in_stock": true, "price_min": 100, "price_max": 200}Two credits, 60 rows. Filters are assembled into eMAG's own path shape for you and then verified against the page eMAG says it served - see the coverage block. Take products[].product_code.
{"code": "DBFC7LYBM", "market": "ro"}One credit. Re-run for this page it returned 7 sellers between 709.99 and 940.71 RON - eMAG itself plus Flanco, Dwyn Shop, BrandGSM, TELETOP, evomag and ForIT.
{"query": "slushalki", "market": "bg"}The same query shape against Bulgaria. Two credits, 60 rows, and the prices come back in EUR - read off the page, not from a market constant.
Five credits for a filtered Romanian listing, the full competing-seller table for one product, and the same segment in a second country.
curl -X POST https://api.reefapi.com/emag/v1/product/offers \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"code":"DBFC7LYBM"}'{
"ok": true,
"data": { … },
"meta": {
"api": "emag",
"endpoint": "product/offers",
"mode": "live",
"latency_ms": …,
"record_count": …
},
"error": null
}eMAG filters go BEFORE the keyword, in one fixed order, in that market's own words
eMAG never errors on a request it will not honour - it substitutes a different listing that still has 60 products on it and answers HTTP 200. Every row below was measured on 2026-09-06. The engine builds the path for you and then checks eMAG's own canonical link to confirm the filters survived; if any were dropped it FAILS the call and names them, instead of handing back rows from a page nobody asked for.
| Path asked for | What eMAG answered | Lesson |
|---|---|---|
| /search/casti/pret,intre-100-si-200 | 60 products, none related to the query | a filter placed AFTER the keyword keeps the filter and throws the KEYWORD away |
| /search/pret,intre-100-si-200/casti | 12,290 results, prices 102.00-199.99 | filters go before the keyword; sort and page go last |
| /televizoare/stoc/rating,star-4/c | 887 products | correct segment order |
| /televizoare/rating,star-4/stoc/c | 2,741 - BOTH filters silently dropped | the order is fixed: brand, then stock, then price, then rating, then sort, then page |
| /frigidere/pret,intre-99999990-si-99999999/c | the whole fridge category | a filter that matches nothing is dropped, not answered as an empty result. The engine turns this into a named failure |
| "in stock", per market | stoc (ro) / stock (bg) / raktaron (hu) | the filter WORDS are localised per storefront and were read off each market's own filter links, never hand-translated. Using stoc on .hu gets it silently dropped |
| a price range, per market | pret,intre-{lo}-si-{hi} (ro) / price,between-{lo}-and-{hi} (bg, hu) | same reason |
| /search/laptop | redirected to /laptopuri/c | a legitimate keyword-to-category redirect, not a dropped constraint. The engine follows it and reports the redirect target in the payload |
Sort is deliberately not one of the checked constraints: eMAG applies it but strips it from the canonical link, which is an SEO canonical. It was proven applied instead of proven echoed - a TV category browse sorted ascending returned 689.00 to 816.75 strictly rising, and a tyre category sorted descending returned 11,736,902.99 down to 4,295.00.
Which markets, which id, which currency, and what happens when a filter does not match
Three storefronts behind one parameter, three currencies, and an id that is a letter-and-digit code rather than a number. Measured on 2026-09-06 across 8 categories, 16 products and all three markets, plus the live calls behind this page. Four of these lines go against us.
emag.ro is the default and by far the largest; emag.bg and emag.hu are the same company's storefronts and all four actions were live-verified on all three. It is one parameter change, not a different integration. A full product URL sets the market from its own domain, so you can pass one of those instead of naming the market.
An eMAG product lives at /<slug>/pd/<CODE>/ and the CODE - something like DBFC7LYBM - is what product/detail and product/offers take. It is per-market: the code that returns headphones on .ro will not resolve on .hu. Category slugs and keywords are per-market too. So carry the market you found an id under, exactly the way you would carry a country with a marketplace item id.
Bulgaria's euro changeover means a hard-coded market currency would already be wrong, and a page that assumed BGN would be quietly reporting the wrong number today. The engine reads the currency from the page on every call: a Bulgarian search re-run for this page returned 60 rows, all EUR, against a total of 35,167. Romania returns RON and Hungary HUF. The currency travels with the price on every row and every offer rather than being inferred from the market.
eMAG is a marketplace and product/offers returns the buybox offer plus every competing seller with that seller's price. Completeness was checked rather than assumed: the number of competing offers returned equalled eMAG's own published count on 16 of 16 measured products, across counts of 0, 2, 5, 6, 7, 9, 10, 11, 13 and 26, and one Hungarian product returned 30 merchants. The buybox row is the rich one - seller rating, stock, delivery estimate, warranty, resealed and used flags - while the competing rows carry name, offer id and price and null for the rest, rather than being back-filled from the buybox.
eMAG never errors on a request it will not honour. It drops the filter and serves the unfiltered listing, with 60 products on it and HTTP 200 - asking for fridges between 99,999,990 and 99,999,999 returns the entire fridge category. Those two outcomes are indistinguishable in the response itself. So the engine compares every constraint you asked for against the page eMAG says it served, and FAILS the call naming the dropped segments. You will occasionally see a call fail where a naive client would have shown you rows; those rows would have been the wrong ones.
A price filter placed after the keyword keeps the filter and throws the keyword away - 60 products, none related to the query, HTTP 200. Two correct filters in the wrong order are both silently dropped and you get a wider, plausible-looking result set. And the filter WORDS differ per storefront, so the in-stock filter is stoc on Romanian, stock on Bulgarian and raktaron on Hungarian; using the Romanian word on the Hungarian site gets it silently dropped. Nothing on eMAG states any of this. The engine builds the path and checks it; you send price_min, brand and in_stock and never see the grammar.
6,000 products, and eMAG's own paginator says so. A category holding 46,687 tyres still stops there. Both numbers are published so you are never guessing which one you are up against: total_pages is what the arithmetic says and reachable_pages is what eMAG will actually serve. Narrow with the price, brand, stock or rating filters rather than paging past it.
Part-way through the build .hu began answering every route, on every exit, with an infrastructure JavaScript challenge while .ro and .bg kept answering normally, and it cleared on its own later in the session. The engine recognises that answer, retries elsewhere and reports a retryable error naming it rather than parsing a challenge page as data. Separately, roughly one exit in six is pre-flagged and gets skipped automatically at a cost of about half a second, with the count reported per call - a mixed 40-call run still came back 40 of 40.
The listing route carries only eMAG's merchandising super-category, not the leaf - a pair of AirPods arrives labelled "Phones Acc & Services", not "Casti Wireless". Putting a super-category into a field named category would be quietly wrong, so it is returned as super_category and the leaf is left null there. The leaf name is on the listing's own title when you browse a category, and the full breadcrumb is on product/detail.
eMAG publishes an availability code and a delivery-day estimate, never a stock count. Rating, review count, question count and eMAG's own recommend percentage are published, but the review texts load separately and this engine ships no reviews action. The return period was null on the buybox on 16 of 16 measured products - eMAG publishes it on competing marketplace offers but not on the offer it fronts itself - so it stays null rather than being filled with the statutory figure. The 30-day lowest price and the manufacturer's RRP appear only where eMAG prints them, 4 of 16 and 6 of 16, because a node reading amount zero and not visible means not published, not zero.
What people build with eMAG
The jobs this data is most often used for.
endpoints
credits per call
Marketplace sellers call product/offers to see every competing seller on their own eMAG listing and the price each is asking.
Regional pricing teams run the same query across ro, bg and hu with one parameter change, and get each market's own currency back with the price.
Category analysts use category with the in-stock and price filters to size a segment, with the honest total and the reachable page count both returned.
Catalog teams call product/detail for the manufacturer part number, the grouped spec table and the EU-mandated 30-day lowest price where eMAG publishes it.
What eMAG data costs
The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.
Full pricing →- 1,000 free credits on signup, no card
- One key, all 188 APIs, one credit pool
- Failed and blocked calls are never charged
- Credits roll over and never expire
Call it in two lines
Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.
curl -X POST https://api.reefapi.com/emag/v1/product/offers \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"code":"DBFC7LYBM"}'import requests
r = requests.post(
"https://api.reefapi.com/emag/v1/product/offers",
headers={"x-api-key": REEF_KEY},
json={
"code": "DBFC7LYBM"
},
)
print(r.json()["data"])Have a question? We got answers.
The questions people actually ask before wiring up eMAG.
Get a free key →Which markets does this cover, and can I use one product code on all of them?▾
Romania (emag.ro), Bulgaria (emag.bg) and Hungary (emag.hu), selected with a market parameter, and all four actions were live-verified on all three. Product codes, category slugs and keywords are per-market and do not carry across - the code that returns headphones on .ro will not resolve on .hu. A full product URL sets the market from its own domain, so you can pass one of those instead.
What currency does emag.bg return?▾
EUR. Bulgaria's euro changeover means a hard-coded market currency would already be wrong, so the engine reads the currency off the page on every call rather than assuming one per storefront. A measured Bulgarian product returned 28.80 EUR; Romania returns RON and Hungary returns HUF. The currency travels with the price on every row and every offer.
Does eMAG really publish competing sellers, and is the list complete?▾
Yes to both. eMAG is a marketplace and product/offers returns the buybox offer plus every competing seller with that seller's price. Completeness was checked rather than assumed: the number of competing offers returned equalled eMAG's own published count on 16 of 16 measured products, across counts of 0, 2, 5, 6, 7, 9, 10, 11, 13 and 26. One Hungarian product returned 30 merchants. The buybox row is the rich one - seller rating, stock, delivery estimate, warranty, genius and resealed/used flags - while the competing rows carry what eMAG publishes for them (name, offer id, price) and null for everything else, rather than being back-filled from the buybox.
How deep can I page?▾
100 pages, or 6,000 products, no matter how large the total is. That is eMAG's own paginator limit - its listings say "1 of 100" - and the response publishes both numbers so you are never guessing: total_pages is what the arithmetic says and reachable_pages is what eMAG will actually serve. A category holding 46,687 tyres still stops at 6,000. Narrow with the price, brand, stock or rating filters instead of paging past it.
Why did my filtered search fail instead of returning zero results?▾
Because eMAG does not return zero results - it drops the filter and serves the unfiltered listing with HTTP 200, and those two are indistinguishable in the response itself. Asking for fridges between 99,999,990 and 99,999,999 returns the entire fridge category. The engine compares every constraint you asked for against the page eMAG says it served and fails the call naming the dropped segments, because handing back 60 unrelated fridges as though they matched your filter is the worse answer.
Why is the leaf category null on a search row?▾
Because the listing route carries only eMAG's merchandising super-category, not the leaf. A pair of AirPods arrives labelled "Phones Acc & Services", not "Casti Wireless". Putting a super-category into a field named category would be quietly wrong, so it is returned as super_category and category is left null there. The leaf name is on the listing's own title when you browse a category, and the full breadcrumb is on product/detail.
What does the engine NOT return?▾
Stock quantity - eMAG publishes an availability code and a delivery-day estimate, never a count. Review bodies - the rating, review count, question count and eMAG's own recommend percentage are published, but the texts load separately and this engine ships no reviews action. The return period on the buybox offer, which was null on 16 of 16 measured products (eMAG publishes it on competing marketplace offers but not on the offer it fronts itself), so it is returned as null rather than filled in with the statutory 14 days. And the 30-day lowest price and manufacturer's RRP appear only where eMAG prints them - 4 of 16 and 6 of 16 - because a node reading amount 0 and not visible means "not published", not zero.
Which market is most likely to need a retry?▾
Hungary. Part-way through the build .hu began answering every route, on every exit, with an AWS WAF JavaScript challenge while .ro and .bg kept answering normally, and it cleared on its own later in the session. The engine recognises that challenge, rotates the exit, and reports a retryable error naming it rather than parsing the challenge page as data. Separately, about one exit in six is pre-flagged by the same WAF; those are skipped automatically at a cost of roughly half a second, the count is reported per call, and a mixed 40-call run came back 40 of 40 successful.
What is the eMAG API?▾
eMAG API is a ReefAPI endpoint group for romanian, bulgarian and hungarian marketplace products and sellers. It returns live JSON through POST requests under /emag/v1.
Is the eMAG API free to try?▾
Yes. ReefAPI starts with 1,000 free credits, no card required. eMAG calls use the same shared credit balance as every other ReefAPI engine.
Do I need an eMAG login or account?▾
No login to eMAG is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the eMAG data?▾
The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the eMAG API use?▾
eMAG actions currently cost 1-2 credits per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.
Can I call eMAG from an AI assistant or MCP client?▾
Yes. Connect ReefAPI once through MCP and your assistant can call emag actions with the same key, credit pool and JSON envelope used by normal REST requests.
39 E-commerce & Marketplaces APIs on the same key
One key, one credit pool, one response envelope. If you are pulling eMAG, you are one call away from the rest of the category — no second contract, no second integration.
Need something this API does not do?
Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.
Try it on your own data before you pay anything
The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 187 APIs, and the same envelope everywhere.
Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-09-06.