---
title: "Search raw offer titles, grouped by product or item key"
method: GET
path: "/api/v1/public/catalog/offers/search"
tags: ["Offers"]
---

# Search raw offer titles, grouped by product or item key

`GET /api/v1/public/catalog/offers/search`

## Request

- Base URL: `https://api.shelfatlas.com`
- URL: `https://api.shelfatlas.com/api/v1/public/catalog/offers/search`
- Auth: HTTP bearer

Danish full-text + typo-tolerant (trigram) search over raw catalog-offer titles, grouped by the curated product when one is known and otherwise by a stable item key. `key` is OPAQUE — `p:<product_id>` or `k:<item_key>` — compare it, never parse it. `item_key` is the group's persisted item identity and is what you pass to GET /offers/history. Supplying lat+lng adds the nearest store per chain and its shelf-status, and orders offers by a worth-the-trip score.

## Query parameters

- `q` string, required
- `lat` number
- `lng` number
- `radius_km` number
- `limit` integer
- `cursor` string
- `order` 'score' | 'relevance'

## Response `200`

Groups of offers matching the query.

- object
  - `ok` true, required
  - `data` object, required
    - `groups` SearchGroup[], required
      - `key` string, required: Opaque group identity: `p:<product_id>` when the group resolved to a curated product, else `k:<item_key>`. Compare it, never parse it.
      - `title` string, required: Normalised display title of the group.
      - `item_key` string, nullable: Persisted item identity of the group's representative offer. Pass this to GET /offers/history.
      - `product_id` string, uuid, nullable: Curated product behind the group, when known.
      - `offers` SearchOffer[], required
        - `offer_id` string, uuid, required
        - `chain_slug` string, required
        - `chain_name` string, required
        - `title` string, required: The offer's raw leaflet title, not the group title.
        - `price_cents` integer, required: Advertised price in øre.
        - `normal_price_cents` integer, nullable
        - `price_kind` 'campaign' | 'regular', required: 'regular' is an everyday shelf price, not an offer.
        - `unit_price_cents` integer, nullable
        - `unit` 'l' | 'kg' | 'stk' | 'null', nullable
        - `valid_from` string, date, required: Europe/Copenhagen calendar date.
        - `valid_to` string, date, required: Inclusive last day, Europe/Copenhagen.
        - `nearest_store` SearchNearestStore
          - `store_id` string, uuid, required
          - `name` string, required
          - `city` string, nullable, required
          - `lat` number, required
          - `lng` number, required
          - `distance_km` number, required
          - `country` string, nullable: ISO country code of the store (e.g. DK, DE) — SA-0077 grænsehandel marking. Not required: absent on a response predating this field.
        - `stock` SearchStock
          - `stock_status` 'in_stock' | 'low_stock' | 'out_of_stock', required
          - `observed_at` string, date-time, required
          - `confidence` string, required
          - `sources` integer, required: Number of distinct sources behind the status.
          - `trusted` boolean: SA-0275: false when only new/unestablished sources back the status (e.g. one first-ever report). Optional for pre-SA-0275 servers.
        - `score` number, required: Worth-the-trip score used for ordering.
        - `image_url` string, nullable: Packshot of the matched product, inherited across the item key when the offer has no match of its own.
        - `crop_url` string, nullable: Our own verified crop of the offer's leaflet tile, for chains where leaflet crops are switched on; null otherwise. Falls between packshot and category illustration in the consumer image layering.
        - `page_box` object, nullable: SA-0282: the cell the clipper chose for this offer, in page-width units (x 0..1, y 0..3 — y is measured in page WIDTHS), present only when crop_url is the whole-page picture so a client can zoom the page onto the offer's field; null otherwise.
          - `x1` number, required
          - `y1` number, required
          - `x2` number, required
          - `y2` number, required
        - `previous_price_cents` integer, nullable: SA-0186: the same item's price in the same chain's previous catalogue, in øre (see Offer.previousPrice); null when unknown.
        - `is_new_this_week` boolean: SA-0186: started within the last 7 days and not offered by the chain in the 21 days before (see Offer.isNewThisWeek).
    - `next_cursor` string, nullable, required
    - `total_offers` integer, required

## Other responses

- `400`: Invalid parameters (`invalid_params`, `incompatible_params`).
- `401`: Missing, invalid, or revoked API key (`missing_api_key` / `invalid_api_key` / `revoked_api_key`).
- `429`: Rate limit or monthly limit reached (`rate_limited` / `monthly_limit_reached`).
- `503`: Temporary backend failure (`service_unavailable`).

---

[API](https://skmtc.dev/shelfatlas/apis/shelfatlas-catalog-api.md) · [All operations](https://skmtc.dev/shelfatlas/apis/shelfatlas-catalog-api/llms.txt) · [OpenAPI document](https://skmtc.dev/shelfatlas/apis/shelfatlas-catalog-api/revisions/943a8773856b?raw)
