# eMAG API scraper — search, category browse, product detail & competing-merchant offers for eMAG, the leading marketplace in Romania (emag.ro), Bulgaria (emag.bg) and Hungary (emag.hu). Prices in RON/EUR/HUF, merchant names, ratings, stock. No account, no browser.

> 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.
> ReefAPI engine `emag` · 4 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/emag/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed or blocked calls are free.
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http, `Authorization: Bearer <key>`) and your assistant can call these actions directly.

## Endpoints

### POST /emag/v1/search — 2 credits
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.

**Parameters:**
- `query` (string, required) — What to search for. Use the market's own language — Romanian on `ro` ('casti bluetooth', 'aspirator'), Bulgarian on `bg`, Hungarian on `hu`. English brand and model names work everywhere.
- `market` (enum, optional, default "ro") — Which eMAG storefront to read: `ro` (emag.ro, Romania — the default and by far the largest), `bg` (emag.bg, Bulgaria) or `hu` (emag.hu, Hungary). Product codes, category slugs and keywords are per-market — a code from emag.ro will not resolve on emag.hu. The currency is read from the page, so rows come back in RON, EUR and HUF respectively. [one of: ro, bg, hu]
- `page` (integer, optional, default 1) — Result page — 1-based, 60 products per page. eMAG stops at page 100 regardless of how many results it claims; narrow with `category` filters rather than paging past that.
- `sort` (enum, optional, default "relevance") — Result ordering. These are eMAG's own seven orderings, taken from the sort menu it publishes with every listing; anything else is rejected rather than passed through, because eMAG answers an unknown sort token with a generic no-results page instead of an error. [one of: relevance, popular, price_asc, price_desc, reviews, discount, newest]
- `price_min` (integer, optional) — Minimum price, in the market's own currency (RON on ro, EUR on bg, HUF on hu). Whole numbers only — that is the granularity eMAG's own price filter accepts.
- `price_max` (integer, optional) — Maximum price, in the market's own currency. Give either bound on its own and the other end is left open.
- `brand` (string, optional) — Brand filter — eMAG's brand slug, lowercase with dashes ('apple', 'jbl', 'blue-star'). Take it from a product's brand link (/brands/brand/<slug>).
- `in_stock` (boolean, optional, default false) — true = only products eMAG currently has in stock. false (default) returns the category as eMAG orders it, out-of-stock rows included.
- `min_rating` (integer, optional) — Only products whose customer rating is at least this many stars (1-5). Products with no rating yet are excluded by eMAG when this is set.
- `max_rotations` (integer, optional, default 6) — Advanced: how many datacentre exits to try before giving up (1-14, default 6). About one exit in ten is pre-flagged by eMAG's WAF and is skipped automatically at a cost of ~0.2 s; the default already makes that vanishingly unlikely to surface. Leave it alone for typical use.

**Returns:** products[]{product_id, product_code, offer_id, title, url, price, currency, lowest_price_30_days, recommended_retail_price, discount_percent, in_stock, availability, delivery_days_estimate, seller{id, name, slug, url, type (emag|marketplace), rating, rating_count (null — eMAG publishes a positive-feedback share, not a count), positive_feedback_percent, fulfilled_by_emag}, rating, review_count, question_count, image, other_offers_count, total_offers_count, campaigns[], super_category (eMAG's merchandising super-category), category (the leaf category name — null on the JSON listing route, which does not carry it; product/detail returns the full breadcrumb)}, count, total (eMAG's own result count), total_pages, reachable_pages (eMAG stops at 100), page, market, redirected_to (the eMAG path actually served when it redirects a keyword to a category, else null) (meta.pagination for paging)

**Example request body:**
```json
{
  "query": "casti bluetooth"
}
```

### POST /emag/v1/category — 2 credits
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.

**Parameters:**
- `category` (string, required) — eMAG category slug — the part before `/c` in a category URL (https://www.emag.ro/casti-bluetooth-telefoane/c → `casti-bluetooth-telefoane`). A full category URL is also accepted. Other live examples: telefoane-mobile · laptopuri · televizoare · aspiratoare · ceasuri-smartwatch.
- `market` (enum, optional, default "ro") — Which eMAG storefront to read: `ro` (emag.ro, Romania — the default and by far the largest), `bg` (emag.bg, Bulgaria) or `hu` (emag.hu, Hungary). Product codes, category slugs and keywords are per-market — a code from emag.ro will not resolve on emag.hu. The currency is read from the page, so rows come back in RON, EUR and HUF respectively. [one of: ro, bg, hu]
- `page` (integer, optional, default 1) — Result page — 1-based, 60 products per page. eMAG stops at page 100 regardless of how many results it claims; narrow with `category` filters rather than paging past that.
- `sort` (enum, optional, default "relevance") — Result ordering. These are eMAG's own seven orderings, taken from the sort menu it publishes with every listing; anything else is rejected rather than passed through, because eMAG answers an unknown sort token with a generic no-results page instead of an error. [one of: relevance, popular, price_asc, price_desc, reviews, discount, newest]
- `price_min` (integer, optional) — Minimum price, in the market's own currency (RON on ro, EUR on bg, HUF on hu). Whole numbers only — that is the granularity eMAG's own price filter accepts.
- `price_max` (integer, optional) — Maximum price, in the market's own currency. Give either bound on its own and the other end is left open.
- `brand` (string, optional) — Brand filter — eMAG's brand slug, lowercase with dashes ('apple', 'jbl', 'blue-star'). Take it from a product's brand link (/brands/brand/<slug>).
- `in_stock` (boolean, optional, default false) — true = only products eMAG currently has in stock. false (default) returns the category as eMAG orders it, out-of-stock rows included.
- `min_rating` (integer, optional) — Only products whose customer rating is at least this many stars (1-5). Products with no rating yet are excluded by eMAG when this is set.
- `max_rotations` (integer, optional, default 6) — Advanced: how many datacentre exits to try before giving up (1-14, default 6). About one exit in ten is pre-flagged by eMAG's WAF and is skipped automatically at a cost of ~0.2 s; the default already makes that vanishingly unlikely to surface. Leave it alone for typical use.

**Returns:** products[]{…same row as search}, count, total, total_pages, reachable_pages, page, title (eMAG's own name for the filtered listing), market, redirected_to

**Example request body:**
```json
{
  "category": "casti-bluetooth-telefoane",
  "sort": "price_desc"
}
```

### POST /emag/v1/product/detail — 1 credit
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.

**Parameters:**
- `code` (string, optional) — The eMAG product code — the token after `/pd/` in a product URL (…/pd/DBFC7LYBM/). Every row `search` and `category` return carries it as `product_code`. Give `code` OR `url`.
- `url` (string, optional) — Full eMAG product URL — the alternative to `code`. The market is taken from the URL's own domain when you pass one.
- `market` (enum, optional, default "ro") — Which eMAG storefront to read: `ro` (emag.ro, Romania — the default and by far the largest), `bg` (emag.bg, Bulgaria) or `hu` (emag.hu, Hungary). Product codes, category slugs and keywords are per-market — a code from emag.ro will not resolve on emag.hu. The currency is read from the page, so rows come back in RON, EUR and HUF respectively. [one of: ro, bg, hu]
- `max_rotations` (integer, optional, default 6) — Advanced: how many datacentre exits to try before giving up (1-14, default 6). About one exit in ten is pre-flagged by eMAG's WAF and is skipped automatically at a cost of ~0.2 s; the default already makes that vanishingly unlikely to surface. Leave it alone for typical use.

**Returns:** product{product_id, product_code, offer_id, title, brand, brand_url, manufacturer_part_number, url, market, price, currency, lowest_price_30_days, recommended_retail_price, discount_percent, promotion_note, in_stock, availability, delivery_days_estimate, free_delivery, fulfilled_by_emag, genius_eligible, warranty_months, return_period_days, condition, seller{id, name, slug, url, type, rating, positive_feedback_percent, fulfilled_by_emag}, rating, review_count, rating_scale (5 — eMAG publishes it in its ld+json AggregateRating), rating_worst, question_count, recommend_percent, images[], description, category, breadcrumb[], specs[]{group, name, value}, other_offers_count, other_offers_min_price}

**Example request body:**
```json
{
  "code": "DBFC7LYBM"
}
```

### POST /emag/v1/product/offers — 1 credit
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.

**Parameters:**
- `code` (string, optional) — The eMAG product code — the token after `/pd/` in a product URL (…/pd/DBFC7LYBM/). Every row `search` and `category` return carries it as `product_code`. Give `code` OR `url`.
- `url` (string, optional) — Full eMAG product URL — the alternative to `code`. The market is taken from the URL's own domain when you pass one.
- `market` (enum, optional, default "ro") — Which eMAG storefront to read: `ro` (emag.ro, Romania — the default and by far the largest), `bg` (emag.bg, Bulgaria) or `hu` (emag.hu, Hungary). Product codes, category slugs and keywords are per-market — a code from emag.ro will not resolve on emag.hu. The currency is read from the page, so rows come back in RON, EUR and HUF respectively. [one of: ro, bg, hu]
- `max_rotations` (integer, optional, default 6) — Advanced: how many datacentre exits to try before giving up (1-14, default 6). About one exit in ten is pre-flagged by eMAG's WAF and is skipped automatically at a cost of ~0.2 s; the default already makes that vanishingly unlikely to surface. Leave it alone for typical use.

**Returns:** offers[]{offer_id, is_buybox, seller{…}, price, currency, lowest_price_30_days, recommended_retail_price, discount_percent, in_stock, availability, delivery_days_estimate, free_delivery, fulfilled_by_emag, genius_eligible, is_resealed, is_used, has_warranty, warranty_months, return_period_days, unit} sorted by price, plus offer_count, min_price, max_price, currency, product{product_code, title, url}

**Example request body:**
```json
{
  "code": "DBFC7LYBM"
}
```

## More
- Try it live, no code: https://reefapi.com/playground?engine=emag
- Human docs page: https://reefapi.com/docs/emag
- Overview page: https://reefapi.com/emag-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
