# dm API scraper — dm-drogerie markt (dm.de …), Europe's largest drugstore chain: search 13 countries, read the full product (price, net/excl-VAT price, price per unit, was-price, EU 30-day lowest price, dm own-brand flag, Payback, GTIN, ingredients, images, rating), plus reviews, online + in-store availability, store finder and autocomplete. No account, no browser.

> Search a dm storefront by keyword and/or category, with the site's own sorts and filters (brand, feature, popular filter, price range). Each row: dm article number (DAN), GTIN, brand, name, the shopper price + currency, the struck was-price + discount, the excl-VAT net price, the price per unit, the EU 30-day lowest price when shown, rating + count, dm own-brand/online-only/new/sale flags, pharmacy flag, variant axis and images. Ingredients and the full description are on product/detail. A no-result search returns an empty list (dm's own signal — no padding); a partly-matching multi-word query keeps the matched rows and reports the unmatched words as modified_query.
> ReefAPI engine `dm` · 6 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/dm/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 calls are free except verified SHEIN NOT_FOUND on product/detail and price (4 credits).
- **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 /dm/v1/search — 1 credit
Search a dm storefront by keyword and/or category, with the site's own sorts and filters (brand, feature, popular filter, price range). Each row: dm article number (DAN), GTIN, brand, name, the shopper price + currency, the struck was-price + discount, the excl-VAT net price, the price per unit, the EU 30-day lowest price when shown, rating + count, dm own-brand/online-only/new/sale flags, pharmacy flag, variant axis and images. Ingredients and the full description are on product/detail. A no-result search returns an empty list (dm's own signal — no padding); a partly-matching multi-word query keeps the matched rows and reports the unmatched words as modified_query.

**Parameters:**
- `country` (enum, optional, default "de") — dm online storefront. All 13 run on one platform; currency + language follow the country. [one of: de, at, cz, sk, hu, si, hr, ro, bg, rs, ba, it, pl]
- `query` (string, optional) — Keyword to search (brand, product or product-type word). Optional when `category` is given (category browse). A GTIN/EAN or a dm article number also resolves to its product.
- `category` (string, optional) — dm category code (allCategories.id, e.g. '110101'), from a detail's category_codes. Browse it alone, or narrow a keyword with it.
- `sort` (enum, optional, default "relevance") — Result ordering. [one of: relevance, price_asc, price_desc, newest, top_rated]
- `page` (integer, optional, default 1) — 1-based page.
- `page_size` (integer, optional, default 30) — Products per page (dm default 30, max honoured here 100).
- `brand` (string, optional) — Brand name filter (exact dm brand name; comma-separate several = any-of).
- `feature` (string, optional) — Product-feature filter (a productFeatures facet value, e.g. 'Vegan', 'Ohne Silikone'). Comma-separate several = all-of.
- `popular_filter` (string, optional) — A popular filter facet value (e.g. 'Lieferbar', 'Marke von dm', 'Neu').
- `price_min` (number, optional) — Minimum price in the country currency.
- `price_max` (number, optional) — Maximum price in the country currency.
- `include_facets` (boolean, optional, default false) — Also return the filter facets (brands, categories, features) with counts.
- `max_rotations` (integer, optional, default 4) — Max datacentre exits to try on a transient throttle.

**Returns:** results[]{dan, gtin, product_name, brand, url, price, currency, was_price, discount_percent, net_price, lowest_price_30d, base_price{amount,unit,per_amount,per_unit,text}, price_notes, rating, rating_count, rating_scale, is_dm_brand, is_pharmacy, online_only, is_new, on_sale, flags, categories, variant_count, variants[{dan,label,hex,url}], image, images}, count, total_results, total_pages, page, page_size, sort, query, country, currency, predicted_category, modified_query, unmatched_terms, facets (with include_facets)

**Example request body:**
```json
{
  "country": "de",
  "query": "shampoo"
}
```

### POST /dm/v1/product/detail — 1 credit
The full dm product by article number, GTIN or URL: name, brand, GTIN, the shopper price with was-price/discount, the excl-VAT net price, the price per unit, the EU 30-day lowest price, the dm-Dauerpreis 'not raised since' date, the Payback line, availability of ratings, the category breadcrumb + codes, the description with bullet points, the ingredient list, the usage text, pills/seals, images, and every size/shade variant.

**Parameters:**
- `country` (enum, optional, default "de") — dm online storefront. All 13 run on one platform; currency + language follow the country. [one of: de, at, cz, sk, hu, si, hr, ro, bg, rs, ba, it, pl]
- `product_id` (string, optional) — dm article number (DAN), e.g. '1569035'. A GTIN/EAN or a dm product URL also works via `gtin`/`url`.
- `gtin` (string, optional) — GTIN/EAN instead of product_id (resolved to the dm article number).
- `url` (string, optional) — A dm.* product URL (…/p/d/<dan>/…) instead of product_id.
- `max_rotations` (integer, optional, default 4) — Max datacentre exits to try on a transient throttle.

**Returns:** product{dan, gtin, product_name, brand, url, price, currency, was_price, discount_percent, net_price, lowest_price_30d, base_price, price_notes, price_stable_since, payback{text,earns_points}, rating, rating_count, is_pharmacy, breadcrumb, category_path, category_codes, description, bullet_points, ingredients, usage, sections[{header,text,bullets}], pills, seals, flags, images, variants[{dan,label,hex,url,selected}], variant_count}

**Example request body:**
```json
{
  "country": "de",
  "product_id": "1569035"
}
```

### POST /dm/v1/reviews — 1 credit
Ratings and reviews for a dm product: the average rating and count, the 1–5 star distribution and the 'would recommend' share (per country), plus the full review text — title, body, rating, nickname, date, recommend flag, photos — from the German (dm-de) review deployment.

**Parameters:**
- `product_id` (string, optional) — dm article number (DAN), e.g. '1569035'. A GTIN/EAN or a dm product URL also works via `gtin`/`url`.
- `country` (enum, optional, default "de") — dm online storefront. All 13 run on one platform; currency + language follow the country. [one of: de, at, cz, sk, hu, si, hr, ro, bg, rs, ba, it, pl]
- `limit` (integer, optional, default 20) — Max review texts to return.
- `max_rotations` (integer, optional, default 4) — Max datacentre exits to try on a transient throttle.

**Returns:** dan, country, rating, rating_count, rating_scale, recommend_percent, distribution{1..5:{count,percent}}, reviews[{id,rating,title,text,nickname,submitted_at,recommended,syndicated,photos}], reviews_count, reviews_locale, reviews_note

**Example request body:**
```json
{
  "country": "de",
  "product_id": "1569035"
}
```

### POST /dm/v1/availability — 1 credit
Online deliverability for dm products by article number, and per-store stock when a store id is given. Returns whether each product is purchasable online and, with store_id, the in-store availability status for that dm market.

**Parameters:**
- `product_ids` (string, optional) — One or more dm article numbers (comma-separated).
- `country` (enum, optional, default "de") — dm online storefront. All 13 run on one platform; currency + language follow the country. [one of: de, at, cz, sk, hu, si, hr, ro, bg, rs, ba, it, pl]
- `store_id` (string, optional) — A dm store id (from `stores`); returns per-store stock for the products.
- `max_rotations` (integer, optional, default 4) — Max datacentre exits to try on a transient throttle.

**Returns:** items[{dan, purchasable, online_status, store_status, express_delivery_area}], count, store_id, country

### POST /dm/v1/stores — 1 credit
The dm store finder: list dm markets near a latitude/longitude (or in a bounding box) for a country, or fetch one store by id. Each store: id, number, address, coordinates, phone, opening hours, services, express-pickup flag and popular-times.

**Parameters:**
- `country` (enum, optional, default "de") — dm online storefront. All 13 run on one platform; currency + language follow the country. [one of: de, at, cz, sk, hu, si, hr, ro, bg, rs, ba, it, pl]
- `lat` (number, optional) — Latitude to search dm stores near.
- `lon` (number, optional) — Longitude to search dm stores near.
- `min_results` (integer, optional, default 10) — Minimum number of nearby stores to return.
- `bbox` (string, optional) — Bounding box 'latMax,lonMin,latMin,lonMax' to list dm stores in an area.
- `store_id` (string, optional) — A dm store id (from `stores`); returns per-store stock for the products.
- `max_rotations` (integer, optional, default 4) — Max datacentre exits to try on a transient throttle.

**Returns:** stores[{store_id, store_number, name, address{street,zip,city,region}, lat, lon, phone, url, express_pickup, services, opening_hours[], popular_times}], count, total, country (single store under `store`)

### POST /dm/v1/suggest — 1 credit
dm search autocomplete for a partial query: the ranked keyword suggestions.

**Parameters:**
- `query` (string, optional) — Keyword to search (brand, product or product-type word). Optional when `category` is given (category browse). A GTIN/EAN or a dm article number also resolves to its product.
- `country` (enum, optional, default "de") — dm online storefront. All 13 run on one platform; currency + language follow the country. [one of: de, at, cz, sk, hu, si, hr, ro, bg, rs, ba, it, pl]
- `max_rotations` (integer, optional, default 4) — Max datacentre exits to try on a transient throttle.

**Returns:** suggestions[{term, rank}], count, query, country

**Example request body:**
```json
{
  "country": "de",
  "query": "bal"
}
```

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