On this page

ShelfAtlas for meal-planning and shopping-list apps

What you can build on the catalog, what is measured today and what is not there. The numbers below are read from the live data when this page is rebuilt (at most once an hour). The general reference is on /docs.

Who this is for

The data is the weekly campaign offers printed in the Danish chains' leaflets, extracted and normalised: one price per offer, valid for a date range, at chain level and nationwide. It is not a shelf-price register. If your app suggests meals, builds a shopping list or shows what is on offer for the ingredients in it, this is the data to match against.

Coverage, live

Every chain the platform tracks, with its freshness, the number of campaign offers valid right now that an API key can see, and how many of those carry each field. A dash means the chain has no such offers at the moment, is not listed on /status, or — in the cropUrl column — is not read in crop mode, so the API returns no cut-outs for it. The same freshness rule as /status applies.

ChainStatusLast updatedActive offersvolumeMlunitPricecropUrlproductId
BilkaFresh2026-10-18116613%99%100%0%
LidlFresh2026-10-1478264%100%97%0%
FøtexFresh2026-10-1869724%99%100%0%
Scandinavian ParkFresh2026-10-1355894%98%100%0%
KvicklyStale2026-10-0753547%98%100%0%
fakta TysklandStale2026-10-0737989%99%100%0%
FleggaardStale2026-10-0736784%99%100%0%
PøtzschStale2026-09-2932495%100%100%0%
LøvbjergFresh2026-10-1629149%100%—0%
SuperBrugsenStale2026-10-0722473%99%100%0%
NettoFresh2026-10-1818967%100%97%0%
CalleStale2026-10-0519381%99%100%0%
MenyFresh2026-10-1415478%91%100%0%
Coop 365discountFresh2026-10-1314985%99%100%0%
Rema 1000Fresh2026-10-1613074%96%100%0%
SparFresh2026-10-1410585%92%100%0%
BorderShopStale2026-10-0710499%100%100%0%
Dagli'BrugsenStale2026-10-077289%100%100%0%
LET-KØBFresh2026-10-173694%100%100%0%
Min KøbmandFresh2026-10-146981%99%100%0%

Fields and fill rates

The percentage columns above are the share of a chain's active campaign offers that have the field set:

  • volumeMl — pack size in millilitres, when it could be read from the leaflet text.
  • unitPrice (with unitPriceUnit) — price per kg or litre, when the pack size is known.
  • cropUrl — a cut-out image of the offer. Only chains whose leaflets are read in crop mode have it; the column is a dash (not a low number) for the other chains.
  • productId — the link to a curated product. This is the linking rate; product linking is in progress and the figure is low for the large chains today.
  • unitCount — always set (it defaults to 1), so it has no fill-rate column.

What is not available:

  • Regular (non-campaign) shelf prices: not available
  • EAN / GTIN / retailer SKU on offers: not yet — product linking is in progress (see the productId column above)
  • Store-level prices: not available — offers are chain-level, nationwide
  • Online-only offers and a freshness SLA: not available — /status is the live contract

Brand is not a field of its own; it only appears inside rawName.

Freshness and sync

The pipeline runs hourly and reads a leaflet the day it is published. Offers are immutable and are never deleted, so a sync only has to ask for what is new. Remember the time of your last sync and pass it as ingested_since, one chain at a time:

curl "https://api.shelfatlas.com/api/v1/public/catalog/offers?chain_slug=netto&ingested_since=<ISO of your last sync>" \
  -H "Authorization: Bearer sa_live_<your-key>"

An offer expires at its validTo. Add include_expired=true to read past offers. For history use /offers/history, /price-history and /term-history. There is no freshness SLA; /status shows the live state per chain.

Matching ingredients to offers

GET /api/v1/public/catalog/offers/search?q= matches a free-text ingredient against offer names (Danish full-text search plus trigram similarity). A basic item that is not on offer this week is a gap in the data, not a matching miss: the catalog only holds campaign offers. If a match is wrong, send us 10–20 bad matches; they become the test cases for better matching.

curl "https://api.shelfatlas.com/api/v1/public/catalog/offers/search?q=hakket%20oksek%C3%B8d" \
  -H "Authorization: Bearer sa_live_<your-key>"

Try it with your own list

One request answers "what is on offer for these items in these chains": up to 12 items, 1–3 chains, the top three offers per item and chain. It counts as one request against your quota.

curl "https://api.shelfatlas.com/api/v1/public/catalog/offers/sample?items=mælk,æg,ris&chains=netto,rema" \
  -H "Authorization: Bearer sa_live_<your-key>"

{
  "ok": true,
  "data": {
    "generated_at": "<ISO>",
    "chains": [
      { "chain_slug": "netto", "chain_name": "Netto" },
      { "chain_slug": "rema", "chain_name": "Rema 1000" }
    ],
    "items": [{ "query": "mælk", "per_chain": { "netto": [ /* offer rows */ ] } }],
    "not_on_offer": [{ "query": "ris", "chain_slug": "rema" }]
  }
}

Each offer is the same row /offers returns. The sample is built on the same matching as /offers/search, so move to /offers/search and /offers in production code.

Licence and attribution

You may display the data, derive baskets and comparisons from it, and cache responses where your use case allows. Do not misrepresent where it comes from: credit it as "data from ShelfAtlas". Do not resell or redistribute the raw data in bulk as your own catalog or data product. The data is provided as-is; the retailer's printed leaflet always prevails. The binding text is in the Acceptable Use policy and the terms of service.

Plans

PlanPriceRequests/monthRequests/minPersonal use only
Free0 kr/month1,00060Yes
Hobby49 kr/month5,000120Yes
Standard649 kr/month100,000300No
Growth2499 kr/month500,000600No
Scale4599 kr/month1,000,0001200No

Evaluate on Free; move to Standard when the data goes into your product. Prices carry no VAT (not VAT-registered), month to month. See pricing.