# idealo.de API scraper — Germany's biggest price-comparison site. One call returns EVERY merchant offer for a product (merchant name, merchant id, price, total price including shipping, delivery window, payment methods and the merchant's idealo rating), plus keyword search, category browse and full product records with the complete German spec sheet, variants with EANs, expert test grades and user opinions. Prices in EUR. No account, no browser, no API key at idealo.

> Search idealo.de by keyword and get back comparison products: id, title, image, price floor, how many merchants sell it, the cheapest merchant's name and shipping cost, the German spec highlights and idealo's expert test grade. Feed the `product_id` of any row straight into `product/offers` to get the whole merchant table. idealo renders one page of results server-side (36 rows, or 60 when sorted by price); page 2 onward is drawn in the browser by an API that refuses anonymous callers, so `has_more` is always false and the honest totals (`total_results`, `total_products`) tell you how much exists beyond it.
> ReefAPI engine `idealo` · 4 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/idealo/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed or blocked calls are free.
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http, `Authorization: Bearer <key>`) and your assistant can call these actions directly.

## Endpoints

### POST /idealo/v1/search — 2 credits
Search idealo.de by keyword and get back comparison products: id, title, image, price floor, how many merchants sell it, the cheapest merchant's name and shipping cost, the German spec highlights and idealo's expert test grade. Feed the `product_id` of any row straight into `product/offers` to get the whole merchant table. idealo renders one page of results server-side (36 rows, or 60 when sorted by price); page 2 onward is drawn in the browser by an API that refuses anonymous callers, so `has_more` is always false and the honest totals (`total_results`, `total_products`) tell you how much exists beyond it.

**Parameters:**
- `query` (string, required) — What to search for on idealo.de. German keywords match best (idealo is a German-language site), but brand and model names work as they are — 'bose quietcomfort', 'lego technic', 'nike air max'.
- `sort` (enum, optional, default "relevance") — Result ordering. Only these three exist on idealo's server-rendered pages: every other ordering the site offers (newest, biggest discount, best rated) is applied in the browser by a client API that refuses anonymous calls, and idealo answers an unknown ordering with HTTP 200 and the default list — so this engine rejects one instead of silently ignoring it. Note that price_asc/price_desc change WHAT is searched, not just the order: idealo drops the category it auto-detected for your keyword and sorts across all of them, so the rows widen to 60 and include single-merchant OFFER rows and accessories (searching 'kaffeevollautomat' sorted by price_asc leads with a 0,13 € descaler — measured, not a bug on our side). Each row says in `type` whether it is a comparison PRODUCT or a single OFFER, and `category_id` says where it came from. Sort inside a category with `category/products` instead when you want the cheapest of one kind of thing. [one of: relevance, price_asc, price_desc]

**Returns:** results[]{product_id (null on an OFFER row), row_id, type ('product'|'offer'), title, url, image, category_id, summary, characteristics[], price_from, price_from_display, currency=EUR, offer_count, used_only, offer_key, offer_url, best_offer{seller{id, name, logo}, shipping_costs, shipping_free, delivery_information, free_return_days}, test_grade (German school grade, 1.0 best … 6.0 worst — NOT a 5-star score), review_count, is_bestseller, voucher_code} + count + total_results + total_products + total_offers + category_id + category_name + related_queries[] + has_more

**Example request body:**
```json
{
  "query": "kopfhörer"
}
```

### POST /idealo/v1/category/products — 2 credits
Browse a whole idealo category by its id (or URL) — the same rich rows as `search`, but enumerated from idealo's own product tree instead of a keyword. Use it to sweep a market: every headphone, every washing machine, every LEGO set, with each row's price floor and merchant count. Same one-page limit as search, and `total_products` says how large the category really is.

**Parameters:**
- `category` (string, required) — idealo category id, or a full category URL. The id is the number in /preisvergleich/ProductCategory/<id>.html. Live examples: 2520 headphones · 1941 washing machines · 4012 televisions · 19116 smartphones · 3487 bean-to-cup coffee machines · 22875 running shoes · 9552 LEGO · 3687 cordless drills · 6012 prams. You do not need a category table: every `search` row carries its own category_id, the search response names the category it landed in, and every `product/detail` returns the full category path — so keyword → whole category is two calls.
- `sort` (enum, optional, default "relevance") — Result ordering. Only these three exist on idealo's server-rendered pages: every other ordering the site offers (newest, biggest discount, best rated) is applied in the browser by a client API that refuses anonymous calls, and idealo answers an unknown ordering with HTTP 200 and the default list — so this engine rejects one instead of silently ignoring it. Note that price_asc/price_desc change WHAT is searched, not just the order: idealo drops the category it auto-detected for your keyword and sorts across all of them, so the rows widen to 60 and include single-merchant OFFER rows and accessories (searching 'kaffeevollautomat' sorted by price_asc leads with a 0,13 € descaler — measured, not a bug on our side). Each row says in `type` whether it is a comparison PRODUCT or a single OFFER, and `category_id` says where it came from. Sort inside a category with `category/products` instead when you want the cheapest of one kind of thing. [one of: relevance, price_asc, price_desc]

**Returns:** results[]{…same row shape as search} + count + total_results + total_products + total_offers + category_id + category_name + has_more

**Example request body:**
```json
{
  "category": "2520"
}
```

### POST /idealo/v1/product/detail — 2 credits
The full idealo record for one product: title, brand, the complete category path, every image, the price range across all merchants (price_min / price_max) and how many offers make it up, the new and used price floors, the entire German spec sheet as grouped label/value rows, colour and capacity variants each with their own id, EAN and price range, idealo's expert test summary (how many magazines tested it and the average German grade), the editorial pros and cons, the product FAQ, and the user opinions idealo publishes.

**Parameters:**
- `product_id` (string, required) — idealo product id — the number in /preisvergleich/OffersOfProduct/<id>_-<slug>.html. A full product URL works too; the slug is cosmetic and the bare id resolves on its own. `search` returns this id on every PRODUCT row.

**Returns:** product{product_id, title, brand, url, category_id, category_name, category_path[]{category_id, name, url}, image, images[], price_min, price_max, currency=EUR, offer_count, price_new_from, price_used_from, availability, item_condition, rating (number), review_count, rating_scale, rating_worst, rating_source, test_reports{count, average_grade, grade_scale, testers[]}, highlights[], pros[], cons[], specs[]{group, label, value}, specs_count, variants[]{product_id, title, url, ean, price_min, price_max, offer_count}, variant_count, faq[]{question, answer}, user_reviews[]{author, date, rating, body, pros, cons}}

**Example request body:**
```json
{
  "product_id": "203505482"
}
```

### POST /idealo/v1/product/offers — 1 credit
THE POINT OF THIS ENGINE: every merchant selling one product, in one call. Each offer carries the merchant's name, idealo shop id and shop page, the city the shop ships from, its idealo star rating and how many ratings it has, the item price, the total price including shipping and the shipping cost derived from the two, the delivery window, which carriers deliver it, the free-return terms, the payment methods the shop accepts, and the merchant's own title for the item (which often names the exact colour variant). Sort by item price or by total price including shipping. idealo lists 20 offers per request and this action pages through them up to `max_offers`. Only NEW offers are reachable — idealo puts its used listings behind an encrypted filter token — so `price_used_from` in `product/detail` is where the used floor comes from.

**Parameters:**
- `product_id` (string, required) — idealo product id — the number in /preisvergleich/OffersOfProduct/<id>_-<slug>.html. A full product URL works too; the slug is cosmetic and the bare id resolves on its own. `search` returns this id on every PRODUCT row.
- `max_offers` (integer, optional, default 100) — How many merchant offers to return (1-500, default 100). idealo serves 20 offers per request, so this decides how many requests the call makes: 20 = one, 100 = up to five. Most products have fewer than 40 offers, in which case the call stops as soon as idealo runs out.
- `sort` (enum, optional, default "price") — Offer ordering. `price` sorts on the item price alone; `total_price` is idealo's 'günstigster Gesamtpreis' — item plus shipping — which reorders the table whenever a cheap listing carries expensive delivery. These are the only two orderings idealo honours on this route; anything else is accepted upstream and silently ignored, so it is rejected here. [one of: price, total_price]

**Returns:** product{product_id, title, url, price_min, price_max, currency=EUR, offer_count, price_new_from, price_used_from} + offers[]{position, offer_key, seller{id, name, url, location, type, rating, rating_count}, price (number), price_display, currency=EUR, total_price (number), total_price_display, shipping_cost, offer_title, offer_url, in_stock, delivery_time, delivery_by, delivery_providers[], free_return, payment_methods[], variant_product_id, variant_title, condition='new'} + count + total_offers + seller_count + has_more

**Example request body:**
```json
{
  "product_id": "203505482",
  "max_offers": 40
}
```

## More
- Try it live, no code: https://reefapi.com/playground?engine=idealo
- Human docs page: https://reefapi.com/docs/idealo
- Overview page: https://reefapi.com/idealo-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
