# IKEA Price Watch Across Countries (`superslowsloth/ikea-products`) Actor

Search IKEA by keyword on any of 49 IKEA country sites and get one flat row per product: price in local currency, previous price, promotion label, rating, category, colour, article number, URL. Same article number across markets, so you can compare prices country by country.

- **URL**: https://apify.com/superslowsloth/ikea-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.53 / 1,000 product scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## IKEA Price Watch Across Countries

Search [IKEA](https://www.ikea.com) on any of 49 country sites for a keyword and
get one flat row per product: the price in that country's currency, the
previous price when IKEA shows a markdown, IKEA's promotion label, rating and
review count, category, colours, the article number and the product link. IKEA
uses the **same 8-digit article number in every country**, so running one query
on `gb/en`, `de/de`, `se/sv` and `us/en` gives you the same product's price in
four currencies, ready to join on `item_no`. Run it on a schedule and diff the
dataset to get an **IKEA price-drop watch**.

No account, no API key, no login, no browser. One request returns the whole
search, up to 1,000 products.

### Input

| Field | Notes |
|---|---|
| `query` | What you would type into IKEA's search box (`desk`, `KALLAX`, `lamp`). Required. Use the market's language for generic words (`schreibtisch` on a German site); a product name such as `KALLAX` works everywhere. |
| `market` | The IKEA country site as `country/language`, from a list of 49 (default `gb/en`). Prices come back in that site's currency. |
| `maxItems` | Products to read (default 100, 1 to 1,000), in IKEA's own relevance order. |
| `proxyConfiguration` | Apify proxy by default. |

#### The 1,000 limit

IKEA serves at most **1,000 products per search and has no offset**: `start`,
`page` and `token` are all rejected with HTTP 400 "Unrecognized parameter", and
a size above 1,000 is HTTP 400 "Size parameter cannot exceed 1000". A search that
matches more (`white` matches 4,517 on `gb/en`) returns its first 1,000 only.
The run log says how many products IKEA reported matching, so you know when you
are looking at a slice; narrow the query (`white desk`) to see the rest.

### Output

Prices are numbers in the market's currency (`currency`, read from each record).

| Field | Notes |
|---|---|
| `query`, `market`, `position` | The query and market searched, and the product's 1-based place in IKEA's result order. |
| `item_no` | IKEA's 8-digit article number, **as text**, because it keeps leading zeros (`00586096`). The key for joining the same product across markets. |
| `item_type` | IKEA's record kind, verbatim: `ART` (article) or `SPR` (a product that groups variants). |
| `name`, `type_name` | The brand name (`LAGKAPTEN / ALEX`) and what it is, in the market's language (`Desk`, `Schreibtisch`). |
| `url`, `image_url` | Absolute product page on the market's own site and the main photo. |
| `price`, `currency` | The price IKEA shows and its ISO code. |
| `previous_price` | The struck-through price. **Null when the product has no markdown**, which is most of them (822 of 13,619 products, 6%, in a 48-market sweep), never 0. |
| `previous_price_label`, `price_valid_from`, `price_valid_to` | IKEA's wording for the previous price (`Previous price`, `Vorher`) and the window it states, as ISO dates. Null when there is no previous price. |
| `price_unit` | What the price buys when it is not one item (`2 pack`, `100 pack`). Compare per-unit prices only after reading this. |
| `price_tag`, `price_tag_text` | IKEA's promotion code (`NEW_LOWER_PRICE`, `FAMILY_PRICE`, `TIME_RESTRICTED_OFFER`, `NEW_PRODUCT`, `DISCONTINUED_PRODUCT`, `LOWER_CURRENT_REGULAR_PRICE_IN_STORE`) and its printed label in the market's language. Null when IKEA sends none. |
| `rating`, `review_count` | Star rating out of 5 and the number of ratings. **Null when IKEA has none** (about 27% of products in the sweep), not 0. |
| `badge` | `Best seller`, `Limited edition` and the like, in the market's language. |
| `last_chance`, `last_chance_text`, `online_sellable` | IKEA's own flags. |
| `measurements` | The size line under the name (`140x60 cm`). Null when blank. |
| `category`, `category_path` | The deepest category and the full path, top first. |
| `colors`, `design_text` | Colour names and the variant's finish text. |
| `variant_count`, `min_order_quantity`, `energy_label` | Variants IKEA groups under the product, the minimum order where one applies, and the EU energy class letter for lamps and appliances. |

#### Honest nulls

A field IKEA did not send is `null`, never `0` or `""`. `previous_price`,
`rating`, `review_count`, `badge`, `price_unit`, `measurements`, `energy_label`,
`min_order_quantity` and the promotion fields are null when the source has
nothing.

#### How `previous_price` is read

IKEA sends the struck-through price only as display text (`"1,899"` and
`".00"`), never as a number, and the thousands grouping varies by market
(`1,899`, `1 943`, `1.299`). The actor reads the digits of the whole part plus the
digits of the decimals, and **checks the reading** by applying it to the current
price shown beside it and comparing with IKEA's numeric price. That matched on all
13,619 products of the sweep. If it ever disagrees, or the "previous" price is not
above the current one, `previous_price` is null rather than a guess.

#### What the prices are

The search response gives one price per product. When it is labelled
`FAMILY_PRICE` ("IKEA Family price") it is a member offer; the response does not
say what a non-member pays, so read `price_tag` before treating the number as
the shelf price. Search results carry no stock information (`availability` was
empty on every one of 1,407 products checked), so this actor does not report stock.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.000525** per product row written (`product-scraped`), i.e. $0.525 per
  1,000 products.

### How it fetches

It calls the JSON search API behind IKEA's own site
(`sik.search.blue.cdtapps.com/<country>/<language>/search-result-page`), asking
for `size=<maxItems>` in one request. Measured 2026-10-12: HTTP 200 from a plain
connection and from an Apify datacenter exit held for 30 minutes, no key or cookie
needed. A 1,000-product response is about 6 MB and came back in 1.6 s. A 403,
429 or 5xx is treated as a refusal: the actor rotates to a fresh address and
retries. A 404 (a country/language pair IKEA has no site for) and a 400 are
permanent and are not retried.

Things worth knowing, all measured:

- **Currencies per market.** `gb` GBP, `us` USD, `ca` CAD, `au` AUD, `nz` NZD,
  `ch` CHF, `se` SEK, `dk` DKK, `no` NOK, `pl` PLN, `cz` CZK, `hu` HUF, `ro` RON,
  `rs` RSD, `jp` JPY, `kr` KRW, `cn` CNY, `in` INR, `sg` SGD, `my` MYR, `th` THB,
  `ph` PHP, `sa` SAR, `ae` AED, `il` ILS, `mx` MXN, `cl` CLP, `co` COP, and EUR
  in the euro-area countries (`de`, `at`, `fr`, `be`, `nl`, `es`, `pt`, `it`,
  `ie`, `fi`, `sk`, `hr`, `si`, `lt`, `lv`, `ee`).
- **Expect gaps when joining markets.** The first 300 results for `table` on
  `gb/en` and `de/de` shared 97 article numbers. That mixes assortment with
  ranking (each side was cut at 300), so it shows gaps, not how many products a
  country lacks. Ask for `maxItems` 1000 on both sides to join more.
- **An empty query is refused.** IKEA answers it with the whole catalogue
  (9,892 products on `gb/en`); the actor asks for a search term instead of
  billing for an accident.
- **Match counts differ by market and language.** Search is run in the market's
  own language, so `desk` on `gb/en` matches 260 and `schreibtisch` on `de/de` 240.
- **Order is IKEA's relevance order.** Join on `item_no`, not on `position`.
- **Markets not listed.** `bg/bg`, `gr/el`, `hk/en`, `tw/zh`, `id/id`, `tr/tr`,
  `za/en` and `fr/en` answered HTTP 404 when probed; `ua/uk` answered 200 with no
  products.
- **Not offered.** Sorting, filters, and paging past 1,000 are not inputs because
  the source does not accept them (see above).

# Actor input Schema

## `query` (type: `string`):

What you would type into IKEA's search box (for example desk, KALLAX, or lamp). Write it in the language of the market you pick: schreibtisch on a German site, skrivbord on a Swedish one. A product name such as KALLAX works in every market. Required.

## `market` (type: `string`):

Which IKEA country site to search, as country/language. Prices come back in that site's currency. The same article number identifies the same product across markets, so run the actor once per market to compare prices.

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

Read at most this many products, in IKEA's own relevance order. IKEA serves at most 1000 per search and has no offset, so a search with more matches than this returns only its first products; narrow the query to see the rest. The run's log says how many matches IKEA reported.

## `proxyConfiguration` (type: `object`):

Apify proxy (automatic datacenter exits) is the default. A refused request (403, 429) is retried from a fresh address, which is what the proxy is for.

## Actor input object example

```json
{
  "query": "desk",
  "market": "gb/en",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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 = {
    "query": "desk",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/ikea-products").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 = {
    "query": "desk",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/ikea-products").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 '{
  "query": "desk",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/ikea-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/ikea-products"
        }
    }
}
```

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/P3zOUxeCc9QgdA3TJ/builds/Iz3NWmLSDEgB7TIfd/openapi.json
