# Co-op API scraper — the UK grocery catalogue of coop.co.uk: search products and read the full product record with the shelf price, the Co-op Member price, the per-kg/per-litre unit price, the current deal and when it ends, the pack size and the EAN/UPC barcode of every product. Browse by category, list every live deal, resolve a product by slug, URL or Co-op's own retail code. No account, no browser for search.

> Search Co-op's UK grocery catalogue by keyword. Every product comes back with the barcode (GTIN/EAN), the price without membership, the Co-op Member price kept separately, the pre-deal price when a deal cut it, the computed per-kg or per-litre unit price, the pack size, every live deal with its end date, the category path and whether Co-op lets you order the item online or only shows you a shop. Filter by category, page family or deals; sort by price.
> ReefAPI engine `coop` · 5 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/coop/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/coop/v1/search — 2 credits
Search Co-op's UK grocery catalogue by keyword. Every product comes back with the barcode (GTIN/EAN), the price without membership, the Co-op Member price kept separately, the pre-deal price when a deal cut it, the computed per-kg or per-litre unit price, the pack size, every live deal with its end date, the category path and whether Co-op lets you order the item online or only shows you a shop. Filter by category, page family or deals; sort by price.

**Parameters:**
- `query` (string, required) — What to look for, as a shopper would type it: 'milk', 'cheddar', 'cadbury'. Brand words work. Co-op's own index answers; it covers food, drink and a small household range (3,412 products measured).
- `category` (enum, optional) — Only products in this Co-op grocery category, spelled exactly as `categories` returns it (and as `categories[]` on every row). Any category Co-op adds later is accepted too. [one of: Chocolate, crisps, biscuits and snacks, Beer, wine and spirits, Food cupboard, Chilled and deli, Pizza, pasta and ready meals, Water, juice and soft Drinks, Frozen foods, Breakfast cereals, tea and coffee, Fruit, veg and salad, Meat, fish and poultry, Household essentials, Vegetarian, vegan and gluten free, Bakery and cakes, Health and beauty, Milk, butter and eggs, Free From]
- `product_type` (enum, optional) — Narrow to one of Co-op's three page families. `winePage` and `spiritsPage` rows additionally carry ABV, country, grape and brand. [one of: foodPage, winePage, spiritsPage]
- `deals_only` (boolean, optional, default false) — true: only products Co-op's index flags as being in a deal (370 of 3,412 when measured). Note `in_deal` has a third state, null, on rows where the index does not say — those are excluded by this filter.
- `sort` (enum, optional, default "relevance") — Result order. Co-op's price sorts run on the index's LISTED price, so a product whose deal price is lower still sorts by the listed one — sort on `price` yourself if you need the deal-aware order. [one of: relevance, price_asc, price_desc]
- `page` (integer, optional, default 1) — Result page, 1-based. `page_count` says how many there are; a page past the end comes back empty rather than repeating the last one (measured).
- `page_size` (integer, optional, default 48) — Products per page (1-100, default 48 — the size Co-op's own site asks for).

**Returns:** results[]{product_id (the slug), retail_code (Co-op's own numeric product code; null on the few rows that carry 0), entry_id, title, gtin (zero-padded and check-digit verified), gtin_format ('EAN-13' | 'UPC-A' | 'EAN-8'), ean_raw (exactly what the index held), price (what a shopper pays WITHOUT membership — the deal price when a deal is open to everyone), currency ('GBP'), regular_price (the pre-deal price, only when a deal cut it), price_is_deal, discount_percent, member_price (Co-op Member price when Co-op runs one as a number), member_offer (a member deal with no single number, as Co-op words it), member_program, member_discount_percent, aldi_price_match, deal_text, deal_ends_at, unit_price, unit ('kg' | 'litre'), unit_price_source, pack_size, brand (wine and spirits only — see coverage), categories[], food_type, product_type, in_deal (true | false | null = the index does not say), promotions[]{deal_id, type ('member' | 'everyone' | 'aldi_price_match'), member_only, text, title, price, starts_at, ends_at, landing_page}, availability ('order_online' | 'in_store_only' | null), order_url, url, image, description, updated_at, first_published_at, and on wine/spirits rows abv_percent, wine_type, grape, origin_country, spirit_type, dietary[], awards[]}, count, total_results (Co-op's own count for the query), page, page_size, page_count, country, currency, query

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

### POST https://api.reefapi.com/coop/v1/category — 2 credits
Walk one Co-op grocery category end to end — same rows as `search`, no keyword. Use it to pull a whole shelf (for example 'Milk, butter and eggs') with barcodes and prices, page by page.

**Parameters:**
- `category` (enum, required) — The Co-op grocery category to walk through, spelled exactly as the `categories` action returns it. [one of: Chocolate, crisps, biscuits and snacks, Beer, wine and spirits, Food cupboard, Chilled and deli, Pizza, pasta and ready meals, Water, juice and soft Drinks, Frozen foods, Breakfast cereals, tea and coffee, Fruit, veg and salad, Meat, fish and poultry, Household essentials, Vegetarian, vegan and gluten free, Bakery and cakes, Health and beauty, Milk, butter and eggs, Free From]
- `product_type` (enum, optional) — Narrow to one of Co-op's three page families. `winePage` and `spiritsPage` rows additionally carry ABV, country, grape and brand. [one of: foodPage, winePage, spiritsPage]
- `deals_only` (boolean, optional, default false) — true: only products Co-op's index flags as being in a deal (370 of 3,412 when measured). Note `in_deal` has a third state, null, on rows where the index does not say — those are excluded by this filter.
- `sort` (enum, optional, default "relevance") — Result order. Co-op's price sorts run on the index's LISTED price, so a product whose deal price is lower still sorts by the listed one — sort on `price` yourself if you need the deal-aware order. [one of: relevance, price_asc, price_desc]
- `page` (integer, optional, default 1) — Result page, 1-based. `page_count` says how many there are; a page past the end comes back empty rather than repeating the last one (measured).
- `page_size` (integer, optional, default 48) — Products per page (1-100, default 48 — the size Co-op's own site asks for).

**Returns:** results[]{product_id (the slug), retail_code (Co-op's own numeric product code; null on the few rows that carry 0), entry_id, title, gtin (zero-padded and check-digit verified), gtin_format ('EAN-13' | 'UPC-A' | 'EAN-8'), ean_raw (exactly what the index held), price (what a shopper pays WITHOUT membership — the deal price when a deal is open to everyone), currency ('GBP'), regular_price (the pre-deal price, only when a deal cut it), price_is_deal, discount_percent, member_price (Co-op Member price when Co-op runs one as a number), member_offer (a member deal with no single number, as Co-op words it), member_program, member_discount_percent, aldi_price_match, deal_text, deal_ends_at, unit_price, unit ('kg' | 'litre'), unit_price_source, pack_size, brand (wine and spirits only — see coverage), categories[], food_type, product_type, in_deal (true | false | null = the index does not say), promotions[]{deal_id, type ('member' | 'everyone' | 'aldi_price_match'), member_only, text, title, price, starts_at, ends_at, landing_page}, availability ('order_online' | 'in_store_only' | null), order_url, url, image, description, updated_at, first_published_at, and on wine/spirits rows abv_percent, wine_type, grape, origin_country, spirit_type, dietary[], awards[]}, count, total_results (Co-op's own count for the query), page, page_size, page_count, country, currency, category

**Example request body:**
```json
{
  "category": "Milk, butter and eggs"
}
```

### POST https://api.reefapi.com/coop/v1/deals — 2 credits
Every Co-op product currently in a deal, with the deal's own wording, price and end date: Co-op Member prices, deals open to everyone and Aldi Price Match lines. Optionally only one kind of deal, or only one category.

**Parameters:**
- `deal_type` (enum, optional) — Only deals of this kind. Omit for every current deal. [one of: member, everyone, aldi_price_match]
- `category` (enum, optional) — Only products in this Co-op grocery category, spelled exactly as `categories` returns it (and as `categories[]` on every row). Any category Co-op adds later is accepted too. [one of: Chocolate, crisps, biscuits and snacks, Beer, wine and spirits, Food cupboard, Chilled and deli, Pizza, pasta and ready meals, Water, juice and soft Drinks, Frozen foods, Breakfast cereals, tea and coffee, Fruit, veg and salad, Meat, fish and poultry, Household essentials, Vegetarian, vegan and gluten free, Bakery and cakes, Health and beauty, Milk, butter and eggs, Free From]
- `sort` (enum, optional, default "relevance") — Result order. Co-op's price sorts run on the index's LISTED price, so a product whose deal price is lower still sorts by the listed one — sort on `price` yourself if you need the deal-aware order. [one of: relevance, price_asc, price_desc]
- `page` (integer, optional, default 1) — Result page, 1-based. `page_count` says how many there are; a page past the end comes back empty rather than repeating the last one (measured).
- `page_size` (integer, optional, default 48) — Products per page (1-100, default 48 — the size Co-op's own site asks for).

**Returns:** results[]{product_id (the slug), retail_code (Co-op's own numeric product code; null on the few rows that carry 0), entry_id, title, gtin (zero-padded and check-digit verified), gtin_format ('EAN-13' | 'UPC-A' | 'EAN-8'), ean_raw (exactly what the index held), price (what a shopper pays WITHOUT membership — the deal price when a deal is open to everyone), currency ('GBP'), regular_price (the pre-deal price, only when a deal cut it), price_is_deal, discount_percent, member_price (Co-op Member price when Co-op runs one as a number), member_offer (a member deal with no single number, as Co-op words it), member_program, member_discount_percent, aldi_price_match, deal_text, deal_ends_at, unit_price, unit ('kg' | 'litre'), unit_price_source, pack_size, brand (wine and spirits only — see coverage), categories[], food_type, product_type, in_deal (true | false | null = the index does not say), promotions[]{deal_id, type ('member' | 'everyone' | 'aldi_price_match'), member_only, text, title, price, starts_at, ends_at, landing_page}, availability ('order_online' | 'in_store_only' | null), order_url, url, image, description, updated_at, first_published_at, and on wine/spirits rows abv_percent, wine_type, grape, origin_country, spirit_type, dietary[], awards[]}, count, total_results (Co-op's own count for the query), page, page_size, page_count, country, currency, deal_type, deal_count_by_type

**Example request body:**
```json
{
  "deal_type": "member"
}
```

### POST https://api.reefapi.com/coop/v1/product/detail — 3 credits
The full record of one Co-op product, read from the product page itself and from Co-op's index together: the price the page is showing today, whether that price is a Member price, the was-price, the unit price exactly as Co-op prints it, the pack size, the barcode with a second independent witness, every live deal, all images, the ordering link and — when the page and the index disagree about the price — both numbers and a `price_mismatch` flag.

**Parameters:**
- `product_id` (string, required) — A product's `product_id` from any listing (the slug, e.g. 'lurpak-spreadable-salted-400g'), its full coop.co.uk product URL, or its numeric `retail_code`.

**Returns:** product{product_id (the slug), retail_code (Co-op's own numeric product code; null on the few rows that carry 0), entry_id, title, gtin (zero-padded and check-digit verified), gtin_format ('EAN-13' | 'UPC-A' | 'EAN-8'), ean_raw (exactly what the index held), price (what a shopper pays WITHOUT membership — the deal price when a deal is open to everyone), currency ('GBP'), regular_price (the pre-deal price, only when a deal cut it), price_is_deal, discount_percent, member_price (Co-op Member price when Co-op runs one as a number), member_offer (a member deal with no single number, as Co-op words it), member_program, member_discount_percent, aldi_price_match, deal_text, deal_ends_at, unit_price, unit ('kg' | 'litre'), unit_price_source, pack_size, brand (wine and spirits only — see coverage), categories[], food_type, product_type, in_deal (true | false | null = the index does not say), promotions[]{deal_id, type ('member' | 'everyone' | 'aldi_price_match'), member_only, text, title, price, starts_at, ends_at, landing_page}, availability ('order_online' | 'in_store_only' | null), order_url, url, image, description, updated_at, first_published_at, and on wine/spirits rows abv_percent, wine_type, grape, origin_country, spirit_type, dietary[], awards[], page_price, page_was_price, page_price_is_member, page_unit_price, page_unit, page_pack_size, page_gtin, page_gtin_format, images[], price_valid_until, minimum_unit_pricing_note, price_mismatch{index_price, page_price, reason}}, sources[]

**Example request body:**
```json
{
  "product_id": "co-op-irresistible-peruvian-coffee-beans"
}
```

### POST https://api.reefapi.com/coop/v1/categories — 1 credit
Co-op's own category counters: every grocery category, food type and page family with the number of products in it, plus how many products are in a deal right now. One cheap call; the values are exactly what `category`, `product_type` and `deals_only` accept.

**Parameters:** none

**Returns:** categories[]{name, product_count}, food_types[]{name, product_count}, product_types[]{name, product_count}, deals{in_deal, not_in_deal, unspecified}, total_products, country, currency

## 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=coop
- Human docs page: https://reefapi.com/docs/coop
- Overview page: https://reefapi.com/coop-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
