# Tesco API scraper — Tesco Groceries in the UK, Ireland, Czechia, Slovakia and Hungary: search products, read the full product record with the shelf price and the Clubcard price kept apart, multi-buy and clearance offers, per-kg unit prices, Aldi Price Match, marketplace sellers, ingredients, allergens and nutrition; browse categories, special offers and reviews. No account, no browser.

> Search a Tesco storefront by keyword. Each product comes with the price without a Clubcard and, separately, the Clubcard price or Clubcard multi-buy when Tesco runs one, the per-kg or per-litre unit price, was-prices of price cuts, clearance, Aldi Price Match, rating, availability, department path and, for marketplace items, the seller and delivery charge. Sort by price; filter by brand, dietary facet, department, special offers or new products.
> ReefAPI engine `tesco` · 7 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/tesco/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 /tesco/v1/search — 1 credit
Search a Tesco storefront by keyword. Each product comes with the price without a Clubcard and, separately, the Clubcard price or Clubcard multi-buy when Tesco runs one, the per-kg or per-litre unit price, was-prices of price cuts, clearance, Aldi Price Match, rating, availability, department path and, for marketplace items, the seller and delivery charge. Sort by price; filter by brand, dietary facet, department, special offers or new products.

**Parameters:**
- `query` (string, required) — What to look for, as a shopper would type it in that country's language ('milk', 'mléko', 'tej'). Brand names work too.
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `page` (integer, optional, default 1) — Result page, 1-based.
- `page_size` (integer, optional, default 24) — Products per page (1-100, default 24). Sponsored products Tesco adds on top are not counted.
- `sort` (enum, optional, default "relevance") — Result order — the orderings Tesco offers. Tesco sorts by the price its shelf shows, which is the Clubcard price when a product has one: sort by `member_price` if set, else `price`, to reproduce the order. [one of: relevance, price_asc, price_desc]
- `brands` (array, optional) — Only these brands, exactly as `filters_available.brands[].value` spells them. Several brands are OR-ed.
- `dietary` (array, optional) — Lifestyle and dietary facets ('Organic', 'Vegetarian', 'No gluten' …), exactly as `filters_available.dietary[].value` spells them.
- `super_department` (string, optional) — Only this top-level department (a `filters_available.super_departments[].value`).
- `department` (string, optional) — Only this department (a `filters_available.departments[].value`).
- `offers_only` (boolean, optional, default false) — true: only products on a special offer (Clubcard price, multi-buy or price cut).
- `new_only` (boolean, optional, default false) — true: only products Tesco marks as new.
- `include_sponsored` (boolean, optional, default false) — false (default) drops the paid placements Tesco adds to a page; 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 (string), base_product_id, gtin, ean, title, brand, url, image, listing_type ('grocery' | 'marketplace' | 'clothing'), price (the price WITHOUT a Clubcard), currency, unit_price, unit_of_measure, sold_by ('piece' | 'weight'), price_basis ('per_item' | 'estimated_item' — loose produce: price is Tesco's estimate for one average item and unit_price is the real per-kg price | 'per_unit' — catch-weight products: price is per unit_of_measure), average_weight_kg, catch_weight_options[]{weight_kg, price, default}, member_price (Clubcard price when Tesco runs a Clubcard price, else null), member_unit_price, member_unit, member_offer (Clubcard multi-buy text, e.g. 'Any 2 for £4 Clubcard Price'), member_discount_percent, member_program ('Clubcard' | null), original_price (was-price of a non-member price cut), original_unit_price (per-unit was-price on loose produce), discount_percent, multi_buy_offer (non-member multi-buy text), clearance, promotions[]{id, type ('member_price' | 'member_multi_buy' | 'price_cut' | 'multi_buy' | 'clearance' | 'other'), member_only, description, unit_selling_info, before_discount, after_discount, start_date, end_date, qualities[]}, deposit_return_charge, availability ('in_stock' | 'unavailable' | …), in_stock, max_order_quantity, is_new, breadcrumb[], shelf_id, rating, review_count, rating_scale, marketplace, seller{id, name} (marketplace only), delivery{estimated_by, charge} (marketplace only), sponsored, price_match[] (competitors Tesco matches), aldi_price_match, low_everyday_price, low_price_promise}, count, total_results, page, page_size, page_count, sponsored_dropped, filters_available{super_departments[], departments[], brands[], dietary[] ({name, value, count, selected}), offers_count, new_count}, country, currency, query, matched_query, sort_applied

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

### POST /tesco/v1/product/detail — 1 credit
The full Tesco product record by `product_id` or URL: every image, both prices and every promotion, unit price, availability and order limit, GTIN/EAN, ingredients, allergens, the nutrition table, storage, origin, specifications, alcohol and energy information, manufacturer and return addresses, rating distribution, marketplace offers from every seller, and clothing size/colour variants.

**Parameters:**
- `product_id` (string, required) — Tesco product id (the number in a product URL, returned as `product_id` by every listing) or the storefront product URL itself. A URL also sets the country.
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** product{product_id (string), base_product_id, gtin, ean, title, brand, url, image, listing_type ('grocery' | 'marketplace' | 'clothing'), price (the price WITHOUT a Clubcard), currency, unit_price, unit_of_measure, sold_by ('piece' | 'weight'), price_basis ('per_item' | 'estimated_item' — loose produce: price is Tesco's estimate for one average item and unit_price is the real per-kg price | 'per_unit' — catch-weight products: price is per unit_of_measure), average_weight_kg, catch_weight_options[]{weight_kg, price, default}, member_price (Clubcard price when Tesco runs a Clubcard price, else null), member_unit_price, member_unit, member_offer (Clubcard multi-buy text, e.g. 'Any 2 for £4 Clubcard Price'), member_discount_percent, member_program ('Clubcard' | null), original_price (was-price of a non-member price cut), original_unit_price (per-unit was-price on loose produce), discount_percent, multi_buy_offer (non-member multi-buy text), clearance, promotions[]{id, type ('member_price' | 'member_multi_buy' | 'price_cut' | 'multi_buy' | 'clearance' | 'other'), member_only, description, unit_selling_info, before_discount, after_discount, start_date, end_date, qualities[]}, deposit_return_charge, availability ('in_stock' | 'unavailable' | …), in_stock, max_order_quantity, is_new, breadcrumb[], shelf_id, rating, review_count, rating_scale, marketplace, seller{id, name} (marketplace only), delivery{estimated_by, charge} (marketplace only), sponsored, price_match[] (competitors Tesco matches), aldi_price_match, low_everyday_price, low_price_promise, images[], videos[], description, ingredients[], allergens[]{name, values[]}, nutrition{columns[], rows[]{name, values[]}}, guideline_daily_amounts, storage, net_contents, pack_size[], number_of_uses, preparation_and_usage, marketing[], origin[]{title, value}, specifications{name: value}, features[], box_contents, directions, dosage, warnings, safety_warning, additives, recycling_info, other_information, legal_labelling, drained_weight, age_limits, alcohol, energy_class, dimensions, food_icons[], shelf_life, restrictions[], manufacturer_address, distributor_address, importer_address, return_address, rating_distribution, offers[]{product_id, seller, sold_by_tesco, price, …, delivery, promotions[]}, offer_count, variants[]{product_id, gtin, ean, title, attributes{colour, size …}, url}, category_ids{super_department, department, aisle, shelf}}, country

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

### POST /tesco/v1/product/reviews — 1 credit
Customer reviews of one Tesco product, newest first, paged: rating, title, text, date, verified-buyer and syndication flags, plus the overall rating and its distribution.

**Parameters:**
- `product_id` (string, required) — Tesco product id (the number in a product URL, returned as `product_id` by every listing) or the storefront product URL itself. A URL also sets the country.
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `page` (integer, optional, default 1) — Review page, 1-based.
- `page_size` (integer, optional, default 10) — Reviews per page (1-50, default 10), newest first.
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** product_id, title, reviews[]{review_id, rating, rating_scale, title, text, submitted_at, author, verified_buyer, syndicated, syndication_source, incentivised}, count, total_reviews, rating, rating_scale, rating_distribution{1..5}, page, page_size, country

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

### POST /tesco/v1/deals — 1 credit
Tesco's special-offers listing: every product currently on a Clubcard price, multi-buy, price cut or clearance, with the same rows as search, optionally narrowed to one top-level department.

**Parameters:**
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `page` (integer, optional, default 1) — Result page, 1-based.
- `page_size` (integer, optional, default 24) — Products per page (1-100, default 24). Sponsored products Tesco adds on top are not counted.
- `super_department` (string, optional) — Only this top-level department (a `filters_available.super_departments[].value`).
- `include_sponsored` (boolean, optional, default false) — false (default) drops the paid placements Tesco adds to a page; 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 (string), base_product_id, gtin, ean, title, brand, url, image, listing_type ('grocery' | 'marketplace' | 'clothing'), price (the price WITHOUT a Clubcard), currency, unit_price, unit_of_measure, sold_by ('piece' | 'weight'), price_basis ('per_item' | 'estimated_item' — loose produce: price is Tesco's estimate for one average item and unit_price is the real per-kg price | 'per_unit' — catch-weight products: price is per unit_of_measure), average_weight_kg, catch_weight_options[]{weight_kg, price, default}, member_price (Clubcard price when Tesco runs a Clubcard price, else null), member_unit_price, member_unit, member_offer (Clubcard multi-buy text, e.g. 'Any 2 for £4 Clubcard Price'), member_discount_percent, member_program ('Clubcard' | null), original_price (was-price of a non-member price cut), original_unit_price (per-unit was-price on loose produce), discount_percent, multi_buy_offer (non-member multi-buy text), clearance, promotions[]{id, type ('member_price' | 'member_multi_buy' | 'price_cut' | 'multi_buy' | 'clearance' | 'other'), member_only, description, unit_selling_info, before_discount, after_discount, start_date, end_date, qualities[]}, deposit_return_charge, availability ('in_stock' | 'unavailable' | …), in_stock, max_order_quantity, is_new, breadcrumb[], shelf_id, rating, review_count, rating_scale, marketplace, seller{id, name} (marketplace only), delivery{estimated_by, charge} (marketplace only), sponsored, price_match[] (competitors Tesco matches), aldi_price_match, low_everyday_price, low_price_promise}, count, total_results, page, page_size, page_count, sponsored_dropped, filters_available{super_departments[], departments[], brands[], dietary[] ({name, value, count, selected}), offers_count, new_count}, country, currency

### POST /tesco/v1/promotion — 1 credit
Every product in one Tesco promotion (for example all the milks in an 'Any 2 for £4 Clubcard Price' offer), by the promotion id any product row carries.

**Parameters:**
- `promotion_id` (string, required) — A promotion id from any product's `promotions[].id` (e.g. an 'Any 2 for £4 Clubcard Price' offer) — returns every product in that offer.
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `page` (integer, optional, default 1) — Result page, 1-based.
- `page_size` (integer, optional, default 24) — Products per page (1-100, default 24). Sponsored products Tesco adds on top are not counted.
- `include_sponsored` (boolean, optional, default false) — false (default) drops the paid placements Tesco adds to a page; 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:** promotion_id, results[]{product_id (string), base_product_id, gtin, ean, title, brand, url, image, listing_type ('grocery' | 'marketplace' | 'clothing'), price (the price WITHOUT a Clubcard), currency, unit_price, unit_of_measure, sold_by ('piece' | 'weight'), price_basis ('per_item' | 'estimated_item' — loose produce: price is Tesco's estimate for one average item and unit_price is the real per-kg price | 'per_unit' — catch-weight products: price is per unit_of_measure), average_weight_kg, catch_weight_options[]{weight_kg, price, default}, member_price (Clubcard price when Tesco runs a Clubcard price, else null), member_unit_price, member_unit, member_offer (Clubcard multi-buy text, e.g. 'Any 2 for £4 Clubcard Price'), member_discount_percent, member_program ('Clubcard' | null), original_price (was-price of a non-member price cut), original_unit_price (per-unit was-price on loose produce), discount_percent, multi_buy_offer (non-member multi-buy text), clearance, promotions[]{id, type ('member_price' | 'member_multi_buy' | 'price_cut' | 'multi_buy' | 'clearance' | 'other'), member_only, description, unit_selling_info, before_discount, after_discount, start_date, end_date, qualities[]}, deposit_return_charge, availability ('in_stock' | 'unavailable' | …), in_stock, max_order_quantity, is_new, breadcrumb[], shelf_id, rating, review_count, rating_scale, marketplace, seller{id, name} (marketplace only), delivery{estimated_by, charge} (marketplace only), sponsored, price_match[] (competitors Tesco matches), aldi_price_match, low_everyday_price, low_price_promise}, count, total_results, page, page_size, page_count, sponsored_dropped, country, currency

### POST /tesco/v1/category — 1 credit
Every product in one Tesco category (department, aisle or shelf), paged, with the same rows, sorting and filters as search. Category ids come from `categories` or any product.

**Parameters:**
- `category_id` (string, required) — A Tesco category id ('b;…') from `categories`, from a product's `category_ids`, or a row's `shelf_id`.
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `page` (integer, optional, default 1) — Result page, 1-based.
- `page_size` (integer, optional, default 24) — Products per page (1-100, default 24). Sponsored products Tesco adds on top are not counted.
- `sort` (enum, optional, default "relevance") — Result order — the orderings Tesco offers. Tesco sorts by the price its shelf shows, which is the Clubcard price when a product has one: sort by `member_price` if set, else `price`, to reproduce the order. [one of: relevance, price_asc, price_desc]
- `brands` (array, optional) — Only these brands, exactly as `filters_available.brands[].value` spells them. Several brands are OR-ed.
- `dietary` (array, optional) — Lifestyle and dietary facets ('Organic', 'Vegetarian', 'No gluten' …), exactly as `filters_available.dietary[].value` spells them.
- `offers_only` (boolean, optional, default false) — true: only products on a special offer (Clubcard price, multi-buy or price cut).
- `new_only` (boolean, optional, default false) — true: only products Tesco marks as new.
- `include_sponsored` (boolean, optional, default false) — false (default) drops the paid placements Tesco adds to a page; 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 (string), base_product_id, gtin, ean, title, brand, url, image, listing_type ('grocery' | 'marketplace' | 'clothing'), price (the price WITHOUT a Clubcard), currency, unit_price, unit_of_measure, sold_by ('piece' | 'weight'), price_basis ('per_item' | 'estimated_item' — loose produce: price is Tesco's estimate for one average item and unit_price is the real per-kg price | 'per_unit' — catch-weight products: price is per unit_of_measure), average_weight_kg, catch_weight_options[]{weight_kg, price, default}, member_price (Clubcard price when Tesco runs a Clubcard price, else null), member_unit_price, member_unit, member_offer (Clubcard multi-buy text, e.g. 'Any 2 for £4 Clubcard Price'), member_discount_percent, member_program ('Clubcard' | null), original_price (was-price of a non-member price cut), original_unit_price (per-unit was-price on loose produce), discount_percent, multi_buy_offer (non-member multi-buy text), clearance, promotions[]{id, type ('member_price' | 'member_multi_buy' | 'price_cut' | 'multi_buy' | 'clearance' | 'other'), member_only, description, unit_selling_info, before_discount, after_discount, start_date, end_date, qualities[]}, deposit_return_charge, availability ('in_stock' | 'unavailable' | …), in_stock, max_order_quantity, is_new, breadcrumb[], shelf_id, rating, review_count, rating_scale, marketplace, seller{id, name} (marketplace only), delivery{estimated_by, charge} (marketplace only), sponsored, price_match[] (competitors Tesco matches), aldi_price_match, low_everyday_price, low_price_promise}, count, total_results, page, page_size, page_count, sponsored_dropped, filters_available{super_departments[], departments[], brands[], dietary[] ({name, value, count, selected}), offers_count, new_count}, country, currency, category_id, sort_applied

### POST /tesco/v1/categories — 1 credit
A Tesco storefront's category tree: every top-level department with its departments, and the id that `category` reads.

**Parameters:**
- `country` (enum, optional, default "uk") — Which Tesco storefront. Prices come back in that country's currency and the texts in its language. Product ids are per country: a UK id is not found in Ireland. [one of: uk, ie, cz, sk, hu]
- `max_rotations` (integer, optional, default 3) — Advanced: how many times to retry a difficult request (1-6, default 3).

**Returns:** categories[]{id, name, level, children[]{id, name, level}}, count, country

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