# ŞOK Market API scraper — Turkish discount grocery chain (sokmarket.com.tr): search ŞOK's online grocery shelf and ŞOK Ekstra, browse the category tree, list the weekly "Haftanın Fırsatları" deals and ŞOK's campaigns with their products, and read one product — price, struck price and discount, stock, brand, origin, net quantity, ingredients, allergens, storage condition, nutrition table and package dimensions. Prices in TRY, scoped to a named ŞOK store. No account, no browser.

> Search ŞOK by keyword in either shop — `market` (the online grocery shelf) or `ekstra` (electronics, home, fashion) — with ŞOK's own sort orders and optional category, brand and price filters. Each row carries the product id, title, brand, URL, images, the store's price, ŞOK's struck price and discount where one exists, stock, the full category path and any campaign badge the product carries.
> ReefAPI engine `sok` · 7 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/sok/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). Send the key as `Authorization: Bearer <key>`, or put it in the URL (`?key=<key>`) when the client has no header field, as ChatGPT does.

## Endpoints

### POST https://api.reefapi.com/sok/v1/search — 1 credit
Search ŞOK by keyword in either shop — `market` (the online grocery shelf) or `ekstra` (electronics, home, fashion) — with ŞOK's own sort orders and optional category, brand and price filters. Each row carries the product id, title, brand, URL, images, the store's price, ŞOK's struck price and discount where one exists, stock, the full category path and any campaign badge the product carries.

**Parameters:**
- `query` (string, required) — What to search for on ŞOK's shelf, in Turkish. ŞOK matches words literally: it does not correct typos and does not pad an unmatched query with unrelated products, so a misspelling returns nothing.
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `page` (integer, optional, default 0) — Zero-based page number (ŞOK's own numbering).
- `page_size` (integer, optional, default 100) — Products per page, 1-500 (default 100).
- `sort` (enum, optional, default "relevance") — The four sort orders ŞOK's own search supports. [one of: relevance, relevance_asc, price_asc, price_desc]
- `category_id` (integer, optional) — Restrict to one ŞOK category id (from the `categories` action or the number at the end of a category URL, …-c-460).
- `brand_id` (integer, optional) — Restrict to one ŞOK brand id (the `brand_id` on any row).
- `min_price` (number, optional) — Only products priced at or above this, in TRY.
- `max_price` (number, optional) — Only products priced at or below this, in TRY.
- `only_campaign_products` (boolean, optional, default false) — Only products carrying a ŞOK campaign badge (ŞOK's own "Kampanyalı Ürünler" filter).
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** results[]{position, product_id, sku_id, offer_id, channel, store_id, title, brand, brand_id, url, image, images[], price (number, TRY), price_before_discount (ŞOK's struck price — null when ŞOK publishes none for that product, which is the case for about 92 % of the shelf), discount_percent (null unless a struck price exists; never estimated), currency=TRY, unit_price (always null — ŞOK publishes no unit price for any product), in_stock, private_label (a ŞOK own-brand product), sales_unit (EACH/KG), min_order_quantity, max_order_quantity, quantity_increment, category, category_id, category_path[{id, name, url}], campaigns[{campaign_id, title, url, starts_on, ends_on}] (starts_on/ends_on are always null — ŞOK publishes no campaign dates), badges[{campaign_id, title, text, display_type}], store_sku_code (ŞOK's internal article number, not a barcode)}, count, total_results, total_pages, page, page_size, has_more, channel, store_id, query, sort, filters_applied

**Example request body:**
```json
{
  "query": "süt",
  "channel": "market"
}
```

### POST https://api.reefapi.com/sok/v1/category/products — 1 credit
Browse one ŞOK category — every product ŞOK lists in it, paged, with the same fields as search and the same optional brand, price and campaign filters. Category ids come from the `categories` action or from the number at the end of a ŞOK category URL.

**Parameters:**
- `category_id` (integer, required) — The ŞOK category to browse — an id from the `categories` action or the number at the end of a category URL (…-c-460 → 460).
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `page` (integer, optional, default 0) — Zero-based page number (ŞOK's own numbering).
- `page_size` (integer, optional, default 100) — Products per page, 1-500 (default 100).
- `sort` (enum, optional, default "relevance") — The four sort orders ŞOK's own search supports. [one of: relevance, relevance_asc, price_asc, price_desc]
- `brand_id` (integer, optional) — Restrict to one ŞOK brand id (the `brand_id` on any row).
- `min_price` (number, optional) — Only products priced at or above this, in TRY.
- `max_price` (number, optional) — Only products priced at or below this, in TRY.
- `only_campaign_products` (boolean, optional, default false) — Only products carrying a ŞOK campaign badge (ŞOK's own "Kampanyalı Ürünler" filter).
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** results[]{position, product_id, sku_id, offer_id, channel, store_id, title, brand, brand_id, url, image, images[], price (number, TRY), price_before_discount (ŞOK's struck price — null when ŞOK publishes none for that product, which is the case for about 92 % of the shelf), discount_percent (null unless a struck price exists; never estimated), currency=TRY, unit_price (always null — ŞOK publishes no unit price for any product), in_stock, private_label (a ŞOK own-brand product), sales_unit (EACH/KG), min_order_quantity, max_order_quantity, quantity_increment, category, category_id, category_path[{id, name, url}], campaigns[{campaign_id, title, url, starts_on, ends_on}] (starts_on/ends_on are always null — ŞOK publishes no campaign dates), badges[{campaign_id, title, text, display_type}], store_sku_code (ŞOK's internal article number, not a barcode)}, count, total_results, total_pages, page, page_size, has_more, channel, store_id, category_id, sort, filters_applied

**Example request body:**
```json
{
  "category_id": 460
}
```

### POST https://api.reefapi.com/sok/v1/categories — 1 credit
ŞOK's whole category tree for a shop — every category with its id, name, level, parent and URL, nested — plus the shortcuts ŞOK links from its own front page (its weekly deal page, new products, gluten-free and so on). Use the ids with `category/products`.

**Parameters:**
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `max_depth` (integer, optional, default 3) — How many levels of the category tree to return (1-6).
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** categories[{category_id, name, level, parent_id, url, children[…]}], category_count, shortcuts[{title, url, kind}], channel, store_id

**Example request body:**
```json
{
  "channel": "market"
}
```

### POST https://api.reefapi.com/sok/v1/product/detail — 1 credit
One ŞOK product by id or URL: the store's price, ŞOK's struck price and discount where one exists, stock, brand, own-brand flag, images, the full category path, campaign badges, the product description, country of origin, net quantity, ingredients, allergen warning, storage condition, usage instructions, the nutrition table and the package dimensions ŞOK publishes.

**Parameters:**
- `product_id` (string, optional) — The ŞOK product id — the number at the end of a product URL (…-p-5834 → 5834). Give this or `url`.
- `url` (string, optional) — A ŞOK product page URL, instead of `product_id`.
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** product{position, product_id, sku_id, offer_id, channel, store_id, title, brand, brand_id, url, image, images[], price (number, TRY), price_before_discount (ŞOK's struck price — null when ŞOK publishes none for that product, which is the case for about 92 % of the shelf), discount_percent (null unless a struck price exists; never estimated), currency=TRY, unit_price (always null — ŞOK publishes no unit price for any product), in_stock, private_label (a ŞOK own-brand product), sales_unit (EACH/KG), min_order_quantity, max_order_quantity, quantity_increment, category, category_id, category_path[{id, name, url}], campaigns[{campaign_id, title, url, starts_on, ends_on}] (starts_on/ends_on are always null — ŞOK publishes no campaign dates), badges[{campaign_id, title, text, display_type}], store_sku_code (ŞOK's internal article number, not a barcode), description, country_of_origin, net_quantity, ingredients, allergen_warning, storage_condition, usage_instructions, other_warnings, food_business_operator, company_registration_number, nutrition_facts[{code, name, value, order}], dimensions{length, width, height, weight, length_unit, weight_unit}, specifications[{name, value}], vat_rate, refund_allowed, variant_id, seller_code}

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

### POST https://api.reefapi.com/sok/v1/campaigns — 2 credits
The products of a ŞOK campaign listing. `weekly_deals` (the default) is ŞOK's own deal page — every product it currently sells below its struck price, with both prices and the discount. `badge_campaigns` is ŞOK's "Kampanyalı Ürünler" filter (1 Alana 1 Bedava, 50 TL ve üzeri…). `campaign` returns one named campaign's products together with its terms; `promotion_group` and `product_group` return ŞOK's own grouped listings.

**Parameters:**
- `campaign` (enum, optional, default "weekly_deals") — Which ŞOK campaign listing to return. `weekly_deals` is the deal page ŞOK links from its own front page. [one of: weekly_deals, badge_campaigns, campaign, promotion_group, product_group]
- `campaign_id` (string, optional) — Required with campaign=campaign: a campaign id from the `campaigns/list` action.
- `group_id` (string, optional) — Required with campaign=promotion_group or campaign=product_group: the id at the end of the group's URL (…-pgrp-<id> / …-sgrp-<id>).
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `page` (integer, optional, default 0) — Zero-based page number (ŞOK's own numbering).
- `page_size` (integer, optional, default 100) — Products per page, 1-500 (default 100).
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** results[]{position, product_id, sku_id, offer_id, channel, store_id, title, brand, brand_id, url, image, images[], price (number, TRY), price_before_discount (ŞOK's struck price — null when ŞOK publishes none for that product, which is the case for about 92 % of the shelf), discount_percent (null unless a struck price exists; never estimated), currency=TRY, unit_price (always null — ŞOK publishes no unit price for any product), in_stock, private_label (a ŞOK own-brand product), sales_unit (EACH/KG), min_order_quantity, max_order_quantity, quantity_increment, category, category_id, category_path[{id, name, url}], campaigns[{campaign_id, title, url, starts_on, ends_on}] (starts_on/ends_on are always null — ŞOK publishes no campaign dates), badges[{campaign_id, title, text, display_type}], store_sku_code (ŞOK's internal article number, not a barcode)}, count, total_results, total_pages, page, page_size, has_more, channel, store_id, campaign, campaign_id, campaign_title, campaign_url, terms (ŞOK's own campaign conditions as text — the only place a campaign date appears, and only as prose), starts_on (null), ends_on (null)

**Example request body:**
```json
{
  "campaign": "weekly_deals"
}
```

### POST https://api.reefapi.com/sok/v1/campaigns/list — 1 credit
ŞOK's campaign catalogue: every campaign currently running with its id, title, conditions, page URL and artwork, plus the campaign groups ŞOK sorts them into. Feed a campaign_id back into the `campaigns` action to get that campaign's products.

**Parameters:**
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `group_id` (string, optional) — Only list campaigns in one ŞOK campaign group (an id from this action's own `groups` list).
- `page` (integer, optional, default 0) — Zero-based page number (ŞOK's own numbering).
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** campaigns[{campaign_id, title, description, url, product_count, starts_on (null), ends_on (null), images[]}], count, total_results, page, has_more, groups[{group_id, name, url}], channel, store_id

**Example request body:**
```json
{
  "group_id": "172"
}
```

### POST https://api.reefapi.com/sok/v1/suggest — 1 credit
ŞOK's own search autocomplete for a partial keyword: the search terms and category suggestions the site offers while the shopper types.

**Parameters:**
- `query` (string, required) — What the shopper has typed so far (2+ characters).
- `channel` (enum, optional, default "market") — Which ŞOK shop to read. The two shops have separate catalogues, separate stores and separate prices. [one of: market, ekstra]
- `store_id` (string, optional) — Advanced: read a named ŞOK store instead of the default online store. Prices are almost always the same across stores but the assortment is not; every answer says which store it came from.
- `max_rotations` (integer, optional, default 5) — Advanced: how many times to retry a difficult request (1-10, default 5).

**Returns:** suggestions[] (search terms), categories[{name, url}], products[] (when ŞOK returns any), count, channel, store_id, query

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

## At scale
- **Volume:** 5M+ requests a day, measured at 60 requests a second across the fleet with no
  central bottleneck. Per-key limits are raised for high-volume accounts; volume pricing on request.
- **Missing a source:** tell us a site we do not cover and it becomes an engine. A customer asked
  for bestprice.gr on 21 Sep 2026 and it was in the catalog on 22 Sep.
- **Support:** 2 minute median time from a question in the live chat to the first answer. Setup
  help included, no support tier to buy.
- **One key, one credit pool** across every API. No per-site plans, no separate subscriptions.

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