# Nordic Grocery Offers - Weekly Deals in DK, NO, SE, FI (`gratifying_graph/nordic-grocery-offers`) Actor

Current grocery offers in Denmark, Norway, Sweden and Finland by city or coordinates: Netto, REMA 1000, føtex, Lidl, KIWI, MENY, ICA, Coop, Willys, Prisma, S-market and more. Prices, pack sizes, unit prices and validity dates in one clean schema.

- **URL**: https://apify.com/gratifying\_graph/nordic-grocery-offers.md
- **Developed by:** [Jimmy A](https://apify.com/gratifying_graph) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 deal results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Nordic Grocery Offers - Weekly Deals in Denmark, Norway, Sweden and Finland

Current **grocery offers from Nordic supermarket chains** near a city or any point in Denmark, Norway, Sweden or Finland, normalized into one clean schema. Search by product or read whole grocery catalogs: Netto, REMA 1000, føtex, Bilka, Lidl, MENY, SPAR, KIWI, Extra, Coop, ICA, Willys, Hemköp, Prisma, S-market, K-Supermarket and more, in a single run.

This actor reads Tjek's public offers API, the data behind the eTilbudsavis, eReklamblad and eTarjouslehdet shopper apps. It is HTTP-first: no browser, no proxies, no login, no key. Offers are local, so you pick a country and a city (or coordinates) and get the offers that run there.

### What you get

One record per offer:

```json
{
  "dealId": "_KmO1yv7A5kn8G1Kai4az",
  "mode": "search",
  "query": "kaffe",
  "country": "DK",
  "city": "Copenhagen",
  "latitude": 55.6761,
  "longitude": 12.5683,
  "radiusKm": 10,
  "name": "Peter Larsen Kaffe Blanding 66",
  "retailer": "REMA 1000",
  "retailerId": "11deC",
  "currentPrice": 35,
  "originalPrice": 60,
  "currency": "DKK",
  "quantityText": "400 g",
  "unitPriceText": "87.50 DKK/kg",
  "description": "Ved køb af flere end 6 stk. pr. dag er prisen 60.00 pr. stk. 400 g 87.50 pr. kg",
  "catalogId": "0PYIjcFX",
  "catalogName": "Uge 39",
  "catalogCategory": "groceries_discount",
  "catalogPage": 25,
  "validFrom": "2026-09-19T22:00:00Z",
  "validTo": "2026-09-26T21:59:59Z",
  "imageUrl": "https://image-transformer-api.tjek.com/?u=s3%3A%2F%2Fsgn-prd-assets%2Fuploads%2F0PYIjcFX%2Fp-25.webp&w=1000&x1r=0.011&x2r=0.506&y1r=0.012&y2r=0.465&s=3567ebfb6edd5db06d0129d2a116cb1d",
  "url": "https://etilbudsavis.dk/REMA-1000?publication=0PYIjcFX&offer=_KmO1yv7A5kn8G1Kai4az",
  "source": "Tjek public offers API",
  "fetchedAt": "2026-09-24T18:23:20.949Z"
}
```

- `currentPrice` is the offer price. `originalPrice` is the before price, only when the retailer published one (about 1 in 6 records in our tests).
- `currency` is the source currency: DKK, NOK, SEK or EUR. Nothing is converted.
- `quantityText` is the pack size as published (`400 g`, `120-500 g`, `24 x 33 cl`). `unitPriceText` is the offer price per kg, litre or piece, computed from the price and an exact pack size. It is null when the offer covers a size range.
- `description` keeps the offer text (variants, limits, printed unit prices) on one line.
- `catalogName` and `catalogCategory` come from the catalog the offer sits in. Grocery catalogs are `groceries`, `groceries_discount` or `hypermarket`. Some catalogs have no name, so `catalogName` is null for about 1 in 10 records.
- `validFrom`/`validTo` are the offer's run dates in UTC. `url` opens the offer on the public shopper site for that country.
- `query` is the search term that matched, or `null` when you read whole catalogs without a term.

### Inputs

- **country** (required): `DK`, `NO`, `SE` or `FI`.
- **city**: Copenhagen, Aarhus, Odense, Aalborg (DK); Oslo, Bergen, Trondheim, Stavanger (NO); Stockholm, Gothenburg, Malmo, Uppsala (SE); Helsinki, Espoo, Tampere, Turku (FI). The search point is the city centre.
- **latitude** / **longitude**: any point inside the chosen country, instead of a city. If both are set, the coordinates win.
- **radiusKm**: stores within this distance of the point, 1-100 (default 10).
- **mode**: `search` (default) finds offers for your terms. `catalogs` reads every offer in the grocery catalogs near the point.
- **queries**: product terms, required in search mode. Local-language terms work best: `kaffe`, `mælk`, `melk`, `mjölk`, `kahvi`, `maito`. In catalogs mode they are optional and keep only offers whose name contains a term.
- **retailers**: only these chains (e.g. `Netto`, `REMA 1000`, `KIWI`, `ICA`). Case, punctuation and Nordic letters do not matter, so `fotex` and `foetex` match `føtex`. A name matches every chain that contains it: `ICA` matches ICA Nära, ICA Kvantum and ICA Maxi Stormarknad.
- **maxItems**: cap on deal records for the whole run (default 200). With several terms or several retailers, the cap is shared evenly between them.
- **onlyWithPrice**: skip offers without a numeric price (default true).

Every input is checked before the run is charged. An unknown country or city, a city in another country, coordinates outside the country, or search mode without a term fails right away at no cost.

### Coverage

Tested on 24 September 2026 in all four countries and all 16 cities. Every city returned offers.

| Country | Search term | Deals per city (10 km) |
|---|---|---|
| Denmark | kaffe | 96-100 (100 is the source cap per term) |
| Denmark | mælk | 49 in Copenhagen |
| Norway | kaffe | 11-13 |
| Sweden | kaffe | 16-25 |
| Finland | kahvi | 22-45 |

Norway returned fewer offers per term than Denmark in the week tested (Oslo: `kaffe` 13, `melk` 3), while the same 10 km around Oslo held 29 grocery catalogs. For Norway, catalogs mode reads the whole weekly catalogs instead.

Coordinates work anywhere inside the country: a point in Roskilde (DK, 5 km) and one in Tromsø (NO) both returned offers.

Retailers that returned deal records in our tests:

- **Denmark**: Netto, REMA 1000, føtex, Bilka, Lidl, MENY, SPAR, Kvickly, SuperBrugsen, Brugsen, Løvbjerg, Min Købmand, LET-KØB, 365discount, Salling, Wolt Market, KortDato.
- **Norway**: KIWI, REMA 1000, Extra, Coop Prix, Joker, Bunnpris, Obs, Europris, Jacobs.
- **Sweden**: ICA Nära, ICA Supermarket, ICA Kvantum, ICA Maxi Stormarknad, Coop, Stora Coop, Coop X:-TRA, Willys, Hemköp, City Gross, Lidl, Matöppet, Matjätten, ÖoB.
- **Finland**: Prisma, S-market, Alepa, Sale, K-Supermarket, Lidl, Tokmanni.

Search also returns non-grocery chains when they advertise a match (e.g. Imerco and Harald Nyborg in Denmark, BAUHAUS and Power in Finland for coffee). `catalogCategory` tells them apart.

### Browse whole grocery catalogs (catalogs mode)

Set `mode` to `catalogs` to get every offer in the grocery catalogs near the point, instead of searching by product. Filter by chain (`retailers`) and optionally by words in the offer name (`queries`).

```json
{ "country": "DK", "city": "Copenhagen", "mode": "catalogs", "retailers": ["Netto"], "maxItems": 500 }
```

- Only grocery catalogs are read (`groceries`, `groceries_discount`, `hypermarket`).
- Catalogs running now come first, then catalogs already published for the coming days. `validFrom` shows when an offer starts.
- In our tests Copenhagen had 44 grocery catalogs within 10 km. Netto alone had two (this week and next week) with 354 offers. A Lidl catalog in Helsinki held 122.
- In Sweden many catalogs are per store (e.g. `Willys Göteborg Hvitfeldtsplatsen`), so `catalogName` tells you which store.
- In catalogs mode a term matches any part of the offer name, so `melk` also found `SJOKOMELK` and `KOKOSMELK` in Oslo.

Each run stops at `maxItems` (default 200, about $0.40 at $0.002 per record). A full city sweep runs to thousands of offers (Tjek listed about 6,500 in the 44 grocery catalogs near Copenhagen), so raise it on purpose.

### Use cases

- **Price comparison apps**: this week's coffee offers at Netto, REMA 1000 and føtex side by side
- **Deal alerts**: run on a schedule, diff against last week, notify on a target price
- **Meal-planning and budgeting tools**: pull the week's grocery offers near a shopper
- **Market and CPG research**: track promo depth and frequency by brand, chain and country over time
- **AI agents**: ask "what coffee is on offer near Oslo this week" through the standby API endpoint

### API / Standby mode for AI agents

Call the actor as a synchronous HTTP endpoint:

```
GET /?country=DK&city=Copenhagen&q=kaffe&maxItems=20
```

Browse mode works the same way:

```
GET /?country=NO&city=Oslo&mode=catalogs&retailer=KIWI&maxItems=50
```

Returns `{ count, deals: [...] }`. Use `lat` and `lng` instead of `city` for any point. Repeat `q` or `retailer`, or separate values with commas, for several. `maxItems` defaults to 50 (1-500). A bare `GET /` returns a readiness object with the city list.

Base URL: `https://gratifying-graph--nordic-grocery-offers.apify.actor`. Add your Apify API token as `?token=YOUR_TOKEN` or an `Authorization: Bearer YOUR_TOKEN` header.

Invalid input returns HTTP 400 and is not charged. If Tjek cannot be read, the call returns HTTP 502 and is not charged. In our tests a first call answered in 1.7-2.7 seconds. The same call again within 30 minutes came back from cache in milliseconds.

### Speed

Measured locally:

- A search run: 1-4 seconds (2-7 requests).
- A whole retailer in catalogs mode (Netto in Copenhagen, 354 offers): 3 seconds.
- A term across every grocery catalog near Oslo in catalogs mode: 34 requests, 24 seconds.

The actor reads Tjek one request at a time with a short pause between requests, so larger runs take longer rather than hitting the API harder.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Actor start | $0.0005 |
| Per deal record | $0.002 |
| API call (standby) | $0.02 |

Measured example: `kaffe` and `mælk` in Copenhagen returned 149 deals, $0.2985 per run. Danish weekly grocery catalogs in the week tested ran for 7 days, so one run a week catches each new catalog: about $1.29 a month. The default input (20 deals) costs $0.0405. A full Netto sweep in Copenhagen (354 offers) costs $0.71.

### FAQ

**Where does the data come from?**
Tjek's public offers API, the same data its shopper apps show. Read politely: one request at a time with a short pause. The actor reads logged-out, public, non-personal data only.

**Which search terms work?**
Use the local language. In Copenhagen `kaffe` returned 100 deals and `coffee` returned 28. Terms we tested: Danish `kaffe`, `mælk`; Norwegian `kaffe`, `melk`; Swedish `kaffe`, `mjölk`, `kyckling`; Finnish `kahvi`, `maito`.

**Why at most 100 deals per search term?**
The source returns at most 100 offers per term and location. To go further, name the chains in `retailers` (the search then covers only those chains) or read whole catalogs in catalogs mode.

**Are these in-store shelf prices?**
No. These are offer prices for the stated validity window, as printed in the retailers' catalogs. Regular shelf prices are not part of catalog data. `originalPrice` is only filled when the retailer printed a before price.

**How are multi-packs and multi-buys handled?**
`currentPrice` is the price of the whole offer as published. Example from a real record: Netto `Pepsi Max eller Faxe Kondi sodavand`, `currentPrice` 89, `quantityText` `24 x 33 cl`, `unitPriceText` `11.24 DKK/l`. That matches the `Pr. liter 11,24` printed in `description`. A multi-buy like `2 FOR 90,-` shows as `currentPrice` 90 and `quantityText` `2 x 250 g`. Check `quantityText` before comparing prices.

**Can unitPriceText differ from the printed unit price?**
Yes, sometimes. It is computed from the price and the structured pack size the retailer entered. When the price includes a bottle deposit, or the pack size was entered wrong, the printed unit price in `description` is the one to trust. It is null when the pack size is a range (`120-500 g`).

**Which currencies?**
The currency the source gives: DKK in Denmark, NOK in Norway, SEK in Sweden, EUR in Finland. No conversion.

**Why do some offers start in the future?**
Chains publish next week's catalog a few days early. Those offers are included, with their real `validFrom`.

**Why must coordinates be inside the country?**
The source falls back to Danish offers for points it cannot place, instead of an error. The actor rejects points outside the chosen country before charging.

**Canada, the US or Australia?**
Use the sibling actors [Canada Grocery Deals](https://apify.com/gratifying_graph/canada-grocery-deals), [US Grocery Deals](https://apify.com/gratifying_graph/us-grocery-deals) and [Australia Catalogue Specials](https://apify.com/gratifying_graph/australia-catalogue-specials) - similar record shape.

**CSV export?**
Every Apify dataset exports as CSV, JSON, Excel, or via API.

### Feedback

Found a bug or need a field? Open an issue on the Issues tab. The actor is checked twice a day and issues get a fast fix.

If this actor saves you time, a short review on the Store page helps other people find it.

# Actor input Schema

## `country` (type: `string`):

Offers are local, so pick the country first. DK = Denmark (DKK), NO = Norway (NOK), SE = Sweden (SEK), FI = Finland (EUR).

## `city` (type: `string`):

A major city in the chosen country. The search point is the city centre. For another place, leave this empty and fill latitude and longitude instead (coordinates win if both are set).

## `latitude` (type: `number`):

Optional. Search point latitude, e.g. 55.6761. Must be inside the chosen country. Use together with longitude instead of a city.

## `longitude` (type: `number`):

Optional. Search point longitude, e.g. 12.5683. Must be inside the chosen country.

## `radiusKm` (type: `integer`):

Offers from stores within this distance of the search point.

## `mode` (type: `string`):

search: find offers for your search terms (default). catalogs: every offer in the grocery catalogs near the location, optionally filtered by search terms.

## `queries` (type: `array`):

Products to search for. Local-language terms work best (kaffe, mælk, melk, mjölk, kahvi, maito). Required in search mode. In catalogs mode, optional: only offers whose name contains a term are kept.

## `retailers` (type: `array`):

Only include these chains (e.g. Netto, REMA 1000, KIWI, ICA). Case, punctuation and Nordic letters do not matter, so fotex matches føtex. A name matches every chain that contains it: ICA matches ICA Nära and ICA Maxi. Empty = all chains.

## `maxItems` (type: `integer`):

Cap on the number of deal records for the whole run. You pay per record, so a small cap keeps a test run cheap. In search mode the cap is shared evenly between search terms, and the source returns at most 100 offers per term.

## `onlyWithPrice` (type: `boolean`):

Skip offers that carry no price.

## Actor input object example

```json
{
  "country": "DK",
  "city": "Copenhagen",
  "radiusKm": 10,
  "mode": "search",
  "queries": [
    "kaffe"
  ],
  "retailers": [],
  "maxItems": 20,
  "onlyWithPrice": true
}
```

# Actor output Schema

## `deals` (type: `string`):

All deal records from this run (dataset items).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "country": "DK",
    "city": "Copenhagen",
    "queries": [
        "kaffe"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("gratifying_graph/nordic-grocery-offers").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "country": "DK",
    "city": "Copenhagen",
    "queries": ["kaffe"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("gratifying_graph/nordic-grocery-offers").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "country": "DK",
  "city": "Copenhagen",
  "queries": [
    "kaffe"
  ],
  "maxItems": 20
}' |
apify call gratifying_graph/nordic-grocery-offers --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gratifying_graph/nordic-grocery-offers"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/8Vqz4949haRy2lfAl/builds/ZHYeEe4Bm2JUT0Hda/openapi.json
