Otto.de API

Germany's big general marketplace, one keyword at a time

The Otto.de API returns product data from Otto, one of Germany's largest online retailers, as clean JSON.

no credit card1,000 free credits · instant API key · live in 10 seconds
Missing a Otto.de endpoint, or need a source we don't have yet?Contact us real people · same-day reply.
O
/otto/v1

9 active endpoints. Every call is 1 credit.

  • POST/otto/v1/search
  • POST/otto/v1/browse
  • POST/otto/v1/product
  • POST/otto/v1/deals
  • POST/otto/v1/new_arrivals
  • POST/otto/v1/brands
  • POST/otto/v1/categories
  • +2 more

What Otto.de endpoints does ReefAPI ship?

9 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.

9 endpoints

search

1 cr

Keyword product search on Otto.de with brand, price, rating, condition and availability filte…

required
query
optional
offset, marke, brand, sortierung, sort, preis, price_min, price_max, bewertung, min_rating, zustand, condition, verfuegbarkeit, availability

browse

1 cr

Browse an Otto.de category landing (e.g.

required
category
optional
offset, marke, brand, sortierung, sort, preis, price_min, price_max, bewertung, min_rating, zustand, condition, verfuegbarkeit, availability

product

1 cr

Product facts for one Otto.de item by variation id.

required
id, query
optional
variation_id

deals

1 cr

Current Otto.de deal / sale campaign products (e.g.

required
optional
campaign

new_arrivals

1 cr

Newest products on Otto.de.

required
optional
query, category, offset, marke, brand, preis, price_min, price_max, bewertung, min_rating, zustand, condition, verfuegbarkeit, availability

brands

1 cr

Brand filter links available on an Otto.de category landing page.

required
category
optional

categories

1 cr

Otto.de department tree.

required
optional
parent, category

filters

1 cr

Discover the sort orders and filter query params supported by Otto.de search/browse, with a l…

required
optional

reviews

1 cr

Aggregate customer rating for an Otto.de item.

required
id, query
optional
variation_id

Every parameter, every allowed value →

Otto.de API

3 of 9 endpoints, ready to run

View docs ↗

Keyword search across Otto.de: each card carries the variation id and its parent product id, the full German product name, brand, price in euros, condition, whether it is in stock, the average star rating, how many reviews it has, an image and the product URL.

1 credit1 required · 7 optional
POST/otto/v1/search
ok562 ms · 18 records · sample
{
  "ok": true,
  "meta": {
    "api": "otto",
    "endpoint": "search",
    "mode": "live",
    "latency_ms": 561.5,
    "record_count": 18,
    "cache_hit": false,
    "completeness_pct": 100
  },
  "data": {
    "products": [
      {
        "variation_id": "1628116693",
        "product_id": "1628116682",
        "name": "De'Longhi Kaffeevollautomat Magnifica S ECAM21.118.B - Milchaufschäumdüse für Cappuccino, Schwarz, Einfache Bedienung (Direktwahltasten) & Reinigung, Kegelmahlwerk",
        "url": "https://www.otto.de/p/de-longhi-kaffeevollautomat-magnifica-s-ecam21-118-b-milchaufschaeumduese-fuer-cappuccino-schwarz-einfache-bedienung-direktwahltasten-reinigung-kegelmahlwerk-1628116682/?variationId=1628116693",
        "image": "https://i.otto.de/i/otto/179e2d57-9560-5c9f-b6db-f33945681fa2?$responsive_ft2$",
        "brand": "De'Longhi",
        "price": 299,
        "currency": "EUR",
        "condition": "new",
        "availability": "in_stock",
        "rating": 4.5,
        "review_count": 1321,
        "sku": null
      },
      {
        "variation_id": "628362630",
        "product_id": "628362489",
        "name": "SIEMENS Kaffeevollautomat EQ6 plus s400 TE654509DE, Doppeltassenfunktion, Keramikmahlwerk, viele Kaffeespezialitäten, automatische Dampfreinigung, saphirschwarz",
        "url": "https://www.otto.de/p/siemens-kaffeevollautomat-eq6-plus-s400-te654509de-doppeltassenfunktion-keramikmahlwerk-viele-kaffeespezialitaeten-automatische-dampfreinigung-saphirschwarz-628362489/?variationId=628362630",
        "image": "https://i.otto.de/i/otto/36c3fa7f-d418-506f-b3f5-ca9768ff523e?$responsive_ft2$",
        "brand": "SIEMENS",
        "price": 649,
        "currency": "EUR",
        "condition": "new",
        "availability": "in_stock",
        "rating": 4.5,
        "review_count": 3974,
        "sku": null
      },
      {
        "variation_id": "1782291584",
        "product_id": "1782291583",
        "name": "Philips Kaffeevollautomat EP3343/50 3300 Series, 6 Getränke (heiß oder eisgekühlt), LatteGo-Milchsystem, SilentBrew Technologie",
        "url": "https://www.otto.de/p/philips-kaffeevollautomat-ep3343-50-3300-series-6-getraenke-heiss-oder-eisgekuehlt-lattego-milchsystem-silentbrew-technologie-1782291583/?variationId=1782291584",
        "image": "https://i.otto.de/i/otto/70ba47c0-02c9-547f-8682-b3d1927dcf26?$responsive_ft2$",
        "brand": "Philips",
        "price": 399,
        "currency": "EUR",
        "condition": "new",
        "availability": "in_stock",
        "rating": 4.5,
        "review_count": 380,
        "sku": null
      }
    ],
    "offset": 0,
    "page_size": 72,
    "has_more": false,
    "next_offset": null
  }
}
Real response, fetched from the live endpoint with the parameters on the left — trimmed to the first few rows, with seller names left out. Press Try it for the untrimmed response.

How the Otto.de API works

Otto.de is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.

01
Authenticate
x-api-key header

No OAuth app, no request signing, no per-site account. One key covers all 183 engines.

02
Call
POST /otto/v1/…

Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.

03
Pay
1 credit per call

Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.

04
Read
{ ok, data, meta, error }

One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.

From a department down to one product's own record

Otto's paths are German slugs and there is no point guessing them. The tree hands you the real ones, and every later call takes a path or a keyword that came from a previous response.

01categories
POST/otto/v1/categories
{"parent": "technik"}

Real child paths with their names, ready to pass straight to browse or to the brand-filter call.

02browse
POST/otto/v1/browse
{"category": "moebel", "sort": "preis-aufsteigend"}

The department's grid, cheapest first, with the variation id you need for anything about a single item.

03product
POST/otto/v1/product
{"id": "1628116693", "query": "kaffeevollautomat"}

One item's own record. It takes the keyword as well as the id, because the item is located through the result grid rather than addressed on its own.

German marketplace pricing you can walk department by department, with a stable variation id on every row to re-check tomorrow.

request
curl -X POST https://api.reefapi.com/otto/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"iphone"}'
response envelope
{
  "ok": true,
  "data": { … },
  "meta": {
    "api": "otto",
    "endpoint": "search",
    "mode": "live",
    "latency_ms": …,
    "record_count": …
  },
  "error": null
}

Otto's two ids, and why a search page returns more rows than it returns prices

Every Otto row is keyed twice, and only part of a result page arrives fully populated. Rather than silently dropping the thin rows or padding them out, this API returns them as id-only stubs and publishes the fill rate in meta.completeness_pct so you can decide what to do with them. All figures below are from live calls on 2026-08-27.

FieldFormatMeasured
product_id10 digits, the number at the end of /p/<slug>-<id>/1786938266
variation_id10 digits, the ?variationId= on that same URL1786938267 - never equal to product_id, and exactly product_id+1 on 26 of 55 rows
price / currencyJSON number plus a currency code715.52 with currency "EUR"; this tier is DE and EUR only
conditionOtto's Zustand, normalized"new" on all 17 populated rows of a search for iphone
availabilitynormalized stock state"in_stock" on all 17 populated rows
skuin the schema, never filled on this tiernull on 55 of 55 rows
meta.completeness_pctshare of returned rows carrying name, brand and price30.91 on search 'iphone' (17 of 55); 52.94 on browse 'technik/computer' (18 of 34)
offset / page_sizeoffset pagination, not page numberspage size 72, next_offset 72 - page with meta.next_offset

The product and reviews actions take a query alongside the id, because they resolve the item through the search grid rather than through the standalone product page. Pass the keyword the item shows up under; the id on its own is not enough.

One grid per query, and what else rides in it

Measured on a keyword search, a brand-and-price-filtered search, a paging attempt, a category that does not exist, a product fetched under the right keyword and then the wrong one, and a query with no results at all.

There is no page two — Otto serves one grid per query

The offset parameter is kept only for compatibility, and any non-zero value is refused outright rather than answered with a silent repeat of the first grid, which is the failure mode you would never catch in testing. What comes back is a fixed slice of fully-described products off the top of the result page, not the whole catalogue for that term, and it was the same size for every query and category we tried. So you widen coverage by narrowing the question — another keyword, another category path, another filter — and never by paging.

The filters are Otto's filters, and the grid still mixes in rows outside them

A search pinned to one brand with a price band returned rows from two other brands and prices well above the ceiling. The filters were passed to the site and applied by it; the grid Otto serves simply also contains sponsored placements and recommendation tiles, and those do not obey your band. Rows Otto flags as promoted are marked as such in the response, but the safe rule is to filter again on your side before you quote a price range. The same habit is why a query Otto has no matches for comes back as a grid of unrelated recommendations rather than as nothing — real products with real prices, none of them what you asked for. Check that the rows resemble your query before you count them.

A single product needs the keyword it appears under, as well as its id

That is a real constraint rather than a quirk to work around: the item is found through the result grid, so the grid has to be the right one. Pass the id with the keyword it was returned under and you get the record; pass the same id under a different keyword and you get an explicit error telling you exactly that, rather than an empty product you might mistake for a discontinued line.

Review counts and star ratings are available; review text is not, and the response admits it

The reviews call returns the average rating and the total number of reviews, with an empty list of review bodies and a flag saying text is not available here. That flag matters more than it looks: an empty array on its own reads as 'this product has no reviews', which would be false for an item with a thousand of them.

A category path that does not exist falls back to a keyword search, and tells you it did

Browsing a made-up path did not fail — it turned the path into a search phrase and returned products, marking the response as a fallback rather than a category result. That is useful when a path is slightly wrong and a trap if you assume the rows came from the department you asked for. Take paths from the tree and the fallback never fires.

What people build with Otto.de

The jobs this data is most often used for.

9

endpoints

1

credit per call

01

Pricing teams call search and deals to track Otto prices and promotions against competitors.

02

Catalog-enrichment tools use product to fill listings with images, specs and availability.

03

Market analysts use brands and categories to size a segment in the German market.

What Otto.de data costs

The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.

Full pricing →
$0.67–$1.50 / 1,000 credits
  • 1,000 free credits on signup, no card
  • One key, all 183 APIs, one credit pool
  • Failed and blocked calls are never charged
  • Credits roll over and never expire

Call it in two lines

Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.

curl
curl -X POST https://api.reefapi.com/otto/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"iphone"}'
python
import requests

r = requests.post(
    "https://api.reefapi.com/otto/v1/search",
    headers={"x-api-key": REEF_KEY},
    json={
  "query": "iphone"
},
)
print(r.json()["data"])
FAQ

Have a question? We got answers.

The questions people actually ask before wiring up Otto.de.

Get a free key →
Why do most of my Otto search rows have two ids and null everywhere else?

Otto publishes structured product data for only part of the result grid, and the rest of the row lives in markup this tier does not read. A search for 'iphone' returned 55 rows of which 17 carried a name, brand and price; the populated ones were positions 0 through 15 plus position 54. meta.completeness_pct reported 30.91 for that call, so you can detect the situation without inspecting every row.

What is the difference between product_id and variation_id?

product_id is the product page and variation_id is the specific configuration Otto listed - a color, a capacity, a size. They were never identical across 55 measured rows, and on 26 of them variation_id was exactly product_id plus one. The URL carries both, as /p/<slug>-<product_id>/?variationId=<variation_id>. The product and reviews actions take the variation id.

Why does the product action need a query as well as an id?

Otto's standalone product page is not readable on this tier, so the item is located inside the search grid instead, and that means saying which keyword it appears under. Passing id 1786938267 with query 'iphone' returned the full record - name, brand, price 715.52 EUR, rating 5.0 and 654 reviews - with meta.completeness_pct 100.

Does the Otto reviews action return review text?

No, and it says so rather than returning an empty list you might misread. It returns the aggregate - average 5.0 over 654 reviews on a measured item - plus text_available: false and a note explaining that individual review bodies live only on the standalone product page. One quirk to know: meta.record_count on this action echoes the review count, 654, not the number of rows returned, which is zero.

Are sponsored Otto listings included in search results?

Yes, and they are flagged rather than removed. A search for 'iphone' marked 10 of 55 rows sponsored: true, at grid positions 0, 1, 7, 14, 20, 24, 27, 31, 37 and 39. If you are computing price statistics, filter them out yourself, because paid slots are not a random sample of the inventory.

Why does sorting by price not return the prices in order?

The sort does reach Otto and does change the grid, but the subset of rows that arrive with prices is a fixed block near the top of the page rather than the sorted sequence. Browsing technik/computer with sort preis-aufsteigend returned prices 692.27, 1089.00, 439.00, 689.00 and 1634.00 in that order. Treat products[] position as the sorted signal, not the price column. The filters action returns this same caveat as live data.

Why does the deals action sometimes return NOT_FOUND?

Because there was no active sale campaign on the storefront at that moment. The action discovers campaigns live rather than hard-coding slugs, so when Otto is between campaigns you get NOT_FOUND with a message saying none were discovered, and the filters action shows available_campaigns as an empty list. That was the state measured on 2026-08-27.

Is anything in the Otto response in German number format?

Prices are not - they are JSON floats with a decimal point, such as 1349.97. Product names are, because Otto writes them that way and they are passed through verbatim: "Apple iPhone Air Smartphone (16,6 cm/6,5 Zoll, 1000 GB Speicherplatz, 48 MP Kamera)". If you parse specs out of the name, expect the comma.

What is the Otto.de API?

Otto.de API is a ReefAPI endpoint group for otto.de It returns live JSON through POST requests under /otto/v1.

Is the Otto.de API free to try?

Yes. ReefAPI starts with 1,000 free credits, no card required. Otto.de calls use the same shared credit balance as every other ReefAPI engine.

Do I need an Otto.de login or account?

No login to Otto.de is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.

How fresh is the Otto.de data?

The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.

How many credits does the Otto.de API use?

Otto.de actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.

Can I call Otto.de from an AI assistant or MCP client?

Yes. Connect ReefAPI once through MCP and your assistant can call otto actions with the same key, credit pool and JSON envelope used by normal REST requests.

35 E-commerce & Marketplaces APIs on the same key

One key, one credit pool, one response envelope. If you are pulling Otto.de, you are one call away from the rest of the category — no second contract, no second integration.

Need something this API does not do?

Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.

0/4000

No account needed · we reply from [email protected]

Try it on your own data before you pay anything

The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 182 APIs, and the same envelope everywhere.

Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-30.