# Migros API scraper — Migros Sanalmarket (Turkey) online grocery: search products, read the full product record with shelf price, Money-card member price, per-kg unit price, campaign flags and the label sheet (ingredients, allergens, origin), browse categories and the site's discount pages, look products up by barcode. Prices in TRY. No account, no browser.

> Search migros.com.tr by keyword. Each product comes with its shelf price and, separately, the Money-card member price when Migros runs one, the per-kg or per-litre unit price, availability, campaign badges (multi-buy, clearance, basket discount), brand and category. Sort by price, discount or best sellers, and filter by brand, category, discount type or lifestyle facet. Paid placements are removed unless you ask for them.
> ReefAPI engine `migros` · 7 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/migros/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 calls are free except verified SHEIN NOT_FOUND on product/detail and price (4 credits).
- **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 /migros/v1/search — 1 credit
Search migros.com.tr by keyword. Each product comes with its shelf price and, separately, the Money-card member price when Migros runs one, the per-kg or per-litre unit price, availability, campaign badges (multi-buy, clearance, basket discount), brand and category. Sort by price, discount or best sellers, and filter by brand, category, discount type or lifestyle facet. Paid placements are removed unless you ask for them.

**Parameters:**
- `query` (string, required) — What to look for, as a shopper would type it on migros.com.tr. Turkish keywords match best ('peynir', 'zeytinyağı', 'bebek bezi'); brand names work too.
- `page` (integer, optional, default 1) — Result page, 1-based. Migros returns about 30 products per page; read `count`, `total_results` and `page_count` rather than assuming a size.
- `sort` (enum, optional, default "relevance") — Result order. Only orderings migros.com.tr honours without an account; `sort_applied` in the response is Migros's own confirmation. [one of: relevance, best_selling, price_asc, price_desc, discount_percent, discount_amount]
- `brand_ids` (array, optional) — Only these brands (Migros brand ids — every result row and `filters_available.brands` carries them). Several ids are OR-ed.
- `category_ids` (array, optional) — Only these categories (ids from `filters_available.categories` or a row's `category.id` / `breadcrumb[].id`). Several ids are OR-ed.
- `discount` (enum, optional) — Only discounted products of this kind. [one of: money, multi_buy]
- `lifestyle_ids` (array, optional) — Lifestyle/diet facets (e.g. gluten-free, lactose-free, organic, vegan) — ids from `filters_available.lifestyle`.
- `include_sponsored` (boolean, optional, default false) — false (default) drops paid placements. Migros injects them on the first page regardless of your filters and sort, so they would break both; true keeps them, flagged `sponsored: true`.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** results[]{product_id (8-digit sku, string), store_product_id, title, brand{id,name,url}, category{id,name,url}, breadcrumb[], url, image, price (number — the shelf price WITHOUT a Money card), price_display, currency=TRY, original_price (struck-through price when a non-member discount runs, else null), discount_percent, member_price (the Money-card price when Migros runs a member discount, else null), member_price_display, member_discount_percent, member_program ('Money' | null), unit_price (per unit_price_unit, for the non-member price), unit_price_unit ('Kg', 'Lt', …), member_unit_price, sold_by ('piece' | 'weight'), price_per ('piece' | 'kg'), availability ('in_stock' | 'out_of_stock' | 'delisted'), in_stock, max_order_quantity (an order cap, not stock), max_order_grams, order_step_grams, min_order_grams, badges[]{type,text}, multi_buy_offer (e.g. '2.si %80'), clearance, basket_discounts[] (applied in the basket, NOT in price), best_seller{rank,category,text}, rating/review_count/rating_scale (null — Migros publishes no product ratings), sponsored}, count, total_results, page, page_count, sponsored_dropped, sort_applied (Migros's own confirmation of the order used), filters_available{categories[], brands[], discounts[], lifestyle[]}{id,name,count}, title, breadcrumb[], query

**Example request body:**
```json
{
  "query": "peynir"
}
```

### POST /migros/v1/product/detail — 1 credit
The full Migros product record by `product_id` (or product URL): title, brand, breadcrumb, every image, the shelf price, the struck-through price and discount, the Money-card member price kept separate, unit price, availability and order limits, campaign badges, the label sheet (ingredients, allergen warning, net quantity, origin, storage and every other labelled line as `specs`), the supplier details Migros is legally required to show, and the store the prices belong to.

**Parameters:**
- `product_id` (string, required) — Migros product id (the 8-digit sku every search row returns as `product_id`, leading zeros included), or a migros.com.tr product URL.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** product{product_id (8-digit sku, string), store_product_id, title, brand{id,name,url}, category{id,name,url}, breadcrumb[], url, image, price (number — the shelf price WITHOUT a Money card), price_display, currency=TRY, original_price (struck-through price when a non-member discount runs, else null), discount_percent, member_price (the Money-card price when Migros runs a member discount, else null), member_price_display, member_discount_percent, member_program ('Money' | null), unit_price (per unit_price_unit, for the non-member price), unit_price_unit ('Kg', 'Lt', …), member_unit_price, sold_by ('piece' | 'weight'), price_per ('piece' | 'kg'), availability ('in_stock' | 'out_of_stock' | 'delisted'), in_stock, max_order_quantity (an order cap, not stock), max_order_grams, order_step_grams, min_order_grams, badges[]{type,text}, multi_buy_offer (e.g. '2.si %80'), clearance, basket_discounts[] (applied in the basket, NOT in price), best_seller{rank,category,text}, rating/review_count/rating_scale (null — Migros publishes no product ratings), sponsored, images[], barcode (null — Migros does not publish it on the product; use product/barcode to look a product up by EAN), discount_descriptions[], description (label sheet as plain text), specs{label: value}, ingredients, allergen_warning, net_quantity, origin, nutrition (null unless Migros labels nutrition lines), supplier[]{name,value}, variants[], store{store_id, warehouse_id, portfolio}}

**Example request body:**
```json
{
  "product_id": "10101938"
}
```

### POST /migros/v1/product/barcode — 1 credit
Look a product up by the EAN/GTIN barcode on its pack and get the same full record as product/detail — the quickest way to match your own catalogue to Migros prices.

**Parameters:**
- `barcode` (string, required) — The product's EAN/GTIN barcode as printed on the pack (8-14 digits).
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** product{… same as product/detail, with barcode = the EAN you asked for}

### POST /migros/v1/deals — 1 credit
Migros's own discount pages: all discounted products, Money-card member discounts, multi-buy campaigns, Migroskop and 'Gördüğünüze İnanın'. Same rows, sorting and filters as search.

**Parameters:**
- `type` (enum, optional, default "all") — Which of migros.com.tr's own campaign pages to read. [one of: all, money, multi_buy, migroskop, gordugunuze_inanin]
- `page` (integer, optional, default 1) — Result page, 1-based. Migros returns about 30 products per page; read `count`, `total_results` and `page_count` rather than assuming a size.
- `sort` (enum, optional, default "relevance") — Result order. Only orderings migros.com.tr honours without an account; `sort_applied` in the response is Migros's own confirmation. [one of: relevance, best_selling, price_asc, price_desc, discount_percent, discount_amount]
- `brand_ids` (array, optional) — Only these brands (Migros brand ids — every result row and `filters_available.brands` carries them). Several ids are OR-ed.
- `category_ids` (array, optional) — Only these categories (ids from `filters_available.categories` or a row's `category.id` / `breadcrumb[].id`). Several ids are OR-ed.
- `include_sponsored` (boolean, optional, default false) — false (default) drops paid placements. Migros injects them on the first page regardless of your filters and sort, so they would break both; true keeps them, flagged `sponsored: true`.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** results[]{product_id (8-digit sku, string), store_product_id, title, brand{id,name,url}, category{id,name,url}, breadcrumb[], url, image, price (number — the shelf price WITHOUT a Money card), price_display, currency=TRY, original_price (struck-through price when a non-member discount runs, else null), discount_percent, member_price (the Money-card price when Migros runs a member discount, else null), member_price_display, member_discount_percent, member_program ('Money' | null), unit_price (per unit_price_unit, for the non-member price), unit_price_unit ('Kg', 'Lt', …), member_unit_price, sold_by ('piece' | 'weight'), price_per ('piece' | 'kg'), availability ('in_stock' | 'out_of_stock' | 'delisted'), in_stock, max_order_quantity (an order cap, not stock), max_order_grams, order_step_grams, min_order_grams, badges[]{type,text}, multi_buy_offer (e.g. '2.si %80'), clearance, basket_discounts[] (applied in the basket, NOT in price), best_seller{rank,category,text}, rating/review_count/rating_scale (null — Migros publishes no product ratings), sponsored}, count, total_results, page, page_count, sponsored_dropped, sort_applied (Migros's own confirmation of the order used), filters_available{categories[], brands[], discounts[], lifestyle[]}{id,name,count}, title, breadcrumb[], type

**Example request body:**
```json
{
  "type": "money"
}
```

### POST /migros/v1/category — 1 credit
Every product in one Migros category, paged, with the same rows, sorting and filters as search. Category ids come from `categories`, from search filters or from any row's breadcrumb.

**Parameters:**
- `category` (string, required) — A category: its Migros id (from `categories`, a row's `breadcrumb[].id` or `filters_available.categories`), its slug ('peynir-c-6d') or its migros.com.tr URL.
- `page` (integer, optional, default 1) — Result page, 1-based. Migros returns about 30 products per page; read `count`, `total_results` and `page_count` rather than assuming a size.
- `sort` (enum, optional, default "relevance") — Result order. Only orderings migros.com.tr honours without an account; `sort_applied` in the response is Migros's own confirmation. [one of: relevance, best_selling, price_asc, price_desc, discount_percent, discount_amount]
- `brand_ids` (array, optional) — Only these brands (Migros brand ids — every result row and `filters_available.brands` carries them). Several ids are OR-ed.
- `discount` (enum, optional) — Only discounted products of this kind. [one of: money, multi_buy]
- `lifestyle_ids` (array, optional) — Lifestyle/diet facets (e.g. gluten-free, lactose-free, organic, vegan) — ids from `filters_available.lifestyle`.
- `include_sponsored` (boolean, optional, default false) — false (default) drops paid placements. Migros injects them on the first page regardless of your filters and sort, so they would break both; true keeps them, flagged `sponsored: true`.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** results[]{product_id (8-digit sku, string), store_product_id, title, brand{id,name,url}, category{id,name,url}, breadcrumb[], url, image, price (number — the shelf price WITHOUT a Money card), price_display, currency=TRY, original_price (struck-through price when a non-member discount runs, else null), discount_percent, member_price (the Money-card price when Migros runs a member discount, else null), member_price_display, member_discount_percent, member_program ('Money' | null), unit_price (per unit_price_unit, for the non-member price), unit_price_unit ('Kg', 'Lt', …), member_unit_price, sold_by ('piece' | 'weight'), price_per ('piece' | 'kg'), availability ('in_stock' | 'out_of_stock' | 'delisted'), in_stock, max_order_quantity (an order cap, not stock), max_order_grams, order_step_grams, min_order_grams, badges[]{type,text}, multi_buy_offer (e.g. '2.si %80'), clearance, basket_discounts[] (applied in the basket, NOT in price), best_seller{rank,category,text}, rating/review_count/rating_scale (null — Migros publishes no product ratings), sponsored}, count, total_results, page, page_count, sponsored_dropped, sort_applied (Migros's own confirmation of the order used), filters_available{categories[], brands[], discounts[], lifestyle[]}{id,name,count}, title, breadcrumb[], category_slug

### POST /migros/v1/categories — 1 credit
Migros Sanalmarket's top-level shelf: every main category with its id and URL, plus the site's campaign pages (mapped to the `deals` type that reads them).

**Parameters:**
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** categories[]{id, name, slug, url, kind ('category' | 'deals' | 'collection' | 'other'), deals_type}, count

### POST /migros/v1/suggest — 1 credit
Autocomplete as a Migros shopper types: suggested search phrases, matching brands and categories, and the top matching products with prices.

**Parameters:**
- `query` (string, required) — What to look for, as a shopper would type it on migros.com.tr. Turkish keywords match best ('peynir', 'zeytinyağı', 'bebek bezi'); brand names work too.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** suggestions[]{type ('keyword' | 'brand' | 'category'), text, id}, products[]{product_id (8-digit sku, string), store_product_id, title, brand{id,name,url}, category{id,name,url}, breadcrumb[], url, image, price (number — the shelf price WITHOUT a Money card), price_display, currency=TRY, original_price (struck-through price when a non-member discount runs, else null), discount_percent, member_price (the Money-card price when Migros runs a member discount, else null), member_price_display, member_discount_percent, member_program ('Money' | null), unit_price (per unit_price_unit, for the non-member price), unit_price_unit ('Kg', 'Lt', …), member_unit_price, sold_by ('piece' | 'weight'), price_per ('piece' | 'kg'), availability ('in_stock' | 'out_of_stock' | 'delisted'), in_stock, max_order_quantity (an order cap, not stock), max_order_grams, order_step_grams, min_order_grams, badges[]{type,text}, multi_buy_offer (e.g. '2.si %80'), clearance, basket_discounts[] (applied in the basket, NOT in price), best_seller{rank,category,text}, rating/review_count/rating_scale (null — Migros publishes no product ratings), sponsored}, total_products, query

**Example request body:**
```json
{
  "query": "peynir"
}
```

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