eMAG API & Scraper
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.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
Four actions - search, category, product/detail and product/offers - all four live-verified on all three markets. Listing rows carry the eMAG product code, title, current price with its own currency, stock state and delivery-day estimate, the seller behind the offer, the star rating and review count, and eMAG's own result total; those fields were filled on 60 of 60 rows in each of 8 measured categories. product/offers returns the buybox offer plus every competing marketplace seller with that seller's price, and the set was verified complete against eMAG's own count on 16 of 16 products - one Hungarian product returned 30 merchants. product/detail adds the manufacturer part number, the grouped spec table, the image gallery, the EU-mandated lowest price of the last 30 days where eMAG publishes it, and the full breadcrumb. Currency is read off the page rather than assumed from the market, which matters because emag.bg now prices in EUR after Bulgaria's changeover. One ReefAPI key, no eMAG account, no browser.
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.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/emag/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"query": "casti bluetooth"
}
}{
"ok": true,
"meta": {
"api": "emag",
"endpoint": "search",
"mode": "live",
"latency_ms": 1420,
"record_count": 60,
"bytes": 556651,
"cache_hit": false,
"waf_retries": 0,
"page": 1,
"market": "ro",
"total": 31003,
"pagination": {
"page": 1,
"has_more": true,
"total_pages": 100
}
},
"data": {
"products": [
{
"product_id": 94523844,
"product_code": "DK660N3BM",
"offer_id": 245274049,
"title": "Casti wireless, Zoopie®, Casti Bluetooth 5.4, Casti audio in ear cu Noise Cancelling ANC, ENC cu Microfon Dual, Earbuds cu Handsfree, Casti fara fir Waterproof pentru Sport, Headphones cu Mod Transparency & Gaming, Telefon Android & iOS, NEO Pro, Negru",
"url": "https://www.emag.ro/casti-wireless-zoopier-casti-bluetooth-5-4-casti-audio-in-ear-cu-noise-cancelling-anc-enc-cu-microfon-dual-earbuds-cu-handsfree-casti-fara-fir-waterproof-pentru-sport-headphones-cu-mod-transparency-ga/pd/DK660N3BM/",
"price": 119.98,
"currency": "RON",
"lowest_price_30_days": null,
"recommended_retail_price": 411.79,
"discount_percent": 70.9,
"in_stock": true,
"availability": "in_stock",
"delivery_days_estimate": 0,
"seller": {
"id": 107261,
"name": "[redacted-name]",
"slug": "zpsltixf",
"url": "https://www.emag.ro/zpsltixf/107261/v",
"type": "marketplace",
"rating": null,
"rating_count": null,
"positive_feedback_percent": null,
"fulfilled_by_emag": true
},
"rating": 4.89,
"review_count": 590,
"question_count": 8,
"image": "https://s13emagst.akamaized.net/products/94524/94523844/images/res_12ca974a0298daaef061040efee4f6b1.jpg?width=1000&height=1000&hash=DBC6B690BC2CA8DA629F9246E0123F37",
"other_offers_count": null,
"total_offers_count": 1,
"campaigns": null,
"super_category": "Phones Acc & Services",
"category": null
},
{
"product_id": 57011759,
"product_code": "DB9ZZWMBM",
"offer_id": 125362209,
"title": "Casti wireless ROTTER®️ cu reducere a zgomotului (ENC), In-ear Bluetooth, Casti Wireless Audio, Fara fir, Afisaj Digital, Casti Bluetooth 5.4, Autonomie 42 ore, Microfon Audio HD, Control touch, Latenta Wireless 45 ms, IPX4, Sport, Universale, Negru",
"url": "https://www.emag.ro/casti-wireless-rotterr-cu-reducere-a-zgomotului-enc-in-ear-bluetooth-casti-wireless-audio-fara-fir-afisaj-digital-casti-bluetooth-5-4-autonomie-42-ore-microfon-audio-hd-control-touch-latenta-wireless-/pd/DB9ZZWMBM/",
"price": 139.88,
"currency": "RON",
"lowest_price_30_days": null,
"recommended_retail_price": 406.72,
"discount_percent": 65.6,
"in_stock": true,
"availability": "in_stock",
"delivery_days_estimate": 0,
"seller": {
"id": 135617,
"name": "[redacted-name]",
"slug": "galluoxk",
"url": "https://www.emag.ro/galluoxk/135617/v",
"type": "marketplace",
"rating": null,
"rating_count": null,
"positive_feedback_percent": null,
"fulfilled_by_emag": true
},
"rating": 4.91,
"review_count": 3397,
"question_count": 82,
"image": "https://s13emagst.akamaized.net/products/57012/57011759/images/res_b231bfc1d754ce7969987da5192dd543.jpg?width=1000&height=1000&hash=FD4671C91D32EA6E[redacted-phone]F890219",
"other_offers_count": 1,
"total_offers_count": 2,
"campaigns": null,
"super_category": "Phones Acc & Services",
"category": null
},
{
"product_id": 53457332,
"product_code": "DYQP65MBM",
"offer_id": 114764475,
"title": "Casti On Ear Sony WH-CH520B, Wireless, Bluetooth, Microfon, Multipoint connection, Quick Charge, Autonomie 50 ore, Negru",
"url": "https://www.emag.ro/casti-on-ear-sony-wh-ch520b-wireless-bluetooth-microfon-multipoint-connection-quick-charge-autonomie-50-ore-negru-whch520b-ce7/pd/DYQP65MBM/",
"price": 179.99,
"currency": "RON",
"lowest_price_30_days": null,
"recommended_retail_price": 294.86,
"discount_percent": 39,
"in_stock": true,
"availability": "in_stock",
"delivery_days_estimate": 0,
"seller": {
"id": 1,
"name": "eMAG",
"slug": "emag",
"url": "https://www.emag.ro/emag/1/v",
"type": "emag",
"rating": null,
"rating_count": null,
"positive_feedback_percent": null,
"fulfilled_by_emag": true
},
"rating": 4.71,
"review_count": 936,
"question_count": 47,
"image": "https://s13emagst.akamaized.net/products/53458/53457332/images/res_64cc3ef89a16222a64d98161215c6243.jpg?width=1000&height=1000&hash=D37A2D5CBF726D3E169D77972B2C88B0",
"other_offers_count": 1,
"total_offers_count": 2,
"campaigns": [
"Campanie Produse Resigilate Ongoing '25"
],
"super_category": "Phones Acc & Services",
"category": null
}
],
"count": 60,
"total": 31003,
"total_pages": 517,
"reachable_pages": 100,
"title": "Rezultate cautare",
"sort_options": [
"score",
"popularity_cr_objectives",
"price"
],
"page": 1,
"market": "ro",
"redirected_to": null
}
}What the eMAG API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search eMAG by keyword — paginated, sortable, and filterable by price range, brand, in-stock and minimum star rating. Every row carries the eMAG product code, title, current price with its own currency, the EU 'lowest price in the last 30 days' when eMAG publishes it, the MERCHANT selling it, stock state, rating and review count, and how many competing offers exist for the same product. `total` is eMAG's own result count for the filtered set. If eMAG redirects a keyword to one of its categories (it sends 'laptop' to its Laptops category, for example), you get that category's products and `redirected_to` tells you where it went. | Pricing teams call search to search eMAG by keyword. | query, market, page, sort, price_min, ... |
| category | Browse an eMAG category by its slug, with the same filters as `search` — price range, brand, in-stock only, minimum star rating — plus sorting and pagination. Same row shape as `search`. This is the enumeration workhorse: use it when you want everything in a category rather than what a keyword happens to match, and `total` tells you how big the filtered set is before you page through it. | Marketplace operators call category to get browse an eMAG category by its slug, with the same filters as `search`. | category, market, page, sort, price_min, ... |
| product/detail | Full eMAG product by `code` or `url`: title, brand, manufacturer part number, current price and currency, the EU-mandated lowest price of the last 30 days and the manufacturer's RRP when eMAG publishes them, the discount computed from those rather than guessed, the merchant with its own rating and positive-feedback share, stock and delivery estimate, warranty and return period, the full image gallery, the description, the breadcrumb, the complete grouped specification table, and how many competing offers exist. Fields eMAG does not publish come back as null — nothing here is inferred. | Catalog enrichment teams call product/detail to get full eMAG product by `code` or `url`. | code, url, market, max_rotations |
| product/offers | Every merchant selling the same product on eMAG, by `code` or `url` — the buybox offer plus each competing marketplace seller, with that seller's price. eMAG is a marketplace and publishes this set on the product page itself, so one call gives you the whole merchant table for price comparison. The buybox row is complete (seller rating, stock, delivery, warranty, return period); the competing rows carry what eMAG publishes for them — merchant name, offer id and price — and their remaining fields are null rather than filled in with the buybox's values. | Retail analysts call product/offers to get every merchant selling the same product on eMAG, by `code` or `url`. | code, url, market, max_rotations |
Call search from your stack
curl -X POST https://api.reefapi.com/emag/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"query":"casti bluetooth"}'import requests
r = requests.post(
"https://api.reefapi.com/emag/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"query": "casti bluetooth"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/emag/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"query": "casti bluetooth"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.emag.search with {"query":"casti bluetooth"}.Who uses this API and why
- 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.
Questions developers ask before integrating
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.