Products
GET /v1/products lists the entire indexed grocery catalogue in stable id
order. Where search answers “match this query”, the list
endpoint answers “give me everything” - full-catalogue exports, nightly sync
jobs, or category sweeps where relevance and facets do not matter. Requires an
API key and each request costs 50 credits.
Every item comes back in the exact search-hit shape, so a listed product, a hydrated product, and a search hit parse identically.
Request
Section titled “Request”curl -H "X-API-Key: $MEALCP_API_KEY" \ "https://api.mealcp.com/v1/products?country=AT&limit=100"Parameters
Section titled “Parameters”| Param | Type | Default | Description |
|---|---|---|---|
country |
string | - | ISO 3166-1 alpha-2, e.g. AT |
retailer |
string | - | Retailer slug (e.g. billa-at) |
category |
string | - | Category slug at any level; matches the whole subtree |
limit |
int | 100 |
Items per page, max 100 |
cursor |
string | - | Continuation token from a prior response’s next_cursor |
All filters combine with AND. Omit cursor to start from the beginning of
the catalogue.
Valid retailer tokens appear in the retailer_slug facet counts of any
search response; valid category tokens come from
GET /v1/categories.
Response
Section titled “Response”First page for Austria (trimmed to one hit):
{ "found": 40213, "items": [ { "id": "rp_01a0144a41fc7fcba462fffa45cd23a1", "name": "Lindt Excellence Fleur de Sel", "brand": "Lindt", "country_code": "AT", "retailer_slug": "billa-at", "chain_slug": "billa", "quantity_value": 100.0, "quantity_unit": "g", "latest_price": 3.79, "latest_unit_price": 37.9, "latest_unit_price_uom": "kg", "currency": "EUR", "latest_observed_at": "2026-08-18T09:57:59Z", "tags": [], "store_cities": [] } ], "next_cursor": "MzQ1Ng"}Items carry the full hit shape, including category_slug, category_path,
url, and image_url - see the
hit fields table.
Fields
Section titled “Fields”| Field | Type | Description |
|---|---|---|
found |
number | Total catalogue size for the applied filters |
items[] |
array | Products in the search-hit shape |
next_cursor |
string | Token for the next page; null when the walk is done |
Two behaviors worth knowing:
foundis a memoised snapshot total (short cache TTL), not a per-request count - treat it as an estimate for planning, and letnext_cursor: nulldecide when to stop.- Enumeration is gap-free. Items are ordered by a stable internal key, so
walking
next_cursoruntilnullsees no duplicates or gaps even as nightly ingest inserts new rows mid-walk.
Walking the catalogue
Section titled “Walking the catalogue”curl -H "X-API-Key: $MEALCP_API_KEY" \ "https://api.mealcp.com/v1/products?country=AT&limit=100&cursor=MzQ1Ng"Keep requesting with the previous next_cursor until it comes back null.
Each request costs a flat 50 credits regardless of limit, so always use
limit=100 - the most rows per credit - and send the same filters on every
call.
List vs search vs lookup
Section titled “List vs search vs lookup”| Need | Use |
|---|---|
| Everything, in bulk, gap-free | GET /v1/products (this page) |
| Keyword matching, facets, sorting | GET /v1/search |
| One product by id | GET /v1/products/{id} |
An empty-q search can also browse the catalogue, but it paginates with
page numbers - if rows shift between requests, a page-based walk can skip or
repeat products. For durable full-catalogue syncs, prefer the cursor walk.
Freshness
Section titled “Freshness”The list reads live catalogue storage, so it may run slightly ahead of the
search index between reingests - a product can appear here before it matches
in search. Check each item’s latest_observed_at when freshness matters.
Errors
Section titled “Errors”Search’s error semantics apply, plus one endpoint-specific case:
400- malformedcursortoken (passed the wire pattern but does not decode to a catalogue position). Start a fresh walk instead of retrying the same token.