# Autodoc API — the vehicle-fitment car-parts catalogue: pick a car down to its exact engine variant, then get every part that fits it with price, OE cross-reference, manufacturer specifications, EAN, images, rating and reviews — plus part-number and OE-number lookup across 6.7 million parts, on the UK storefront (GBP) or the German one (EUR). No key, account or cookie required

> Walk Autodoc's vehicle tree — this is where every other action starts, because car parts are only meaningful against a specific car. Call it with no parameters for the 52 makers; add `maker` for that maker's model families; add `model` for the generations (the chassis codes: E82, F20, W204); add `generation` for the actual engine variants, each with Autodoc's numeric vehicle id. Add `vehicle` (or pass a `vehicle_url`) and you get the car itself — its full factory description (engine, kW/hp, fuel, build years, engine code) plus every part category Autodoc stocks for it, ready to feed straight into `search`. Set `market` to `de` to walk the German storefront's tree instead; the slugs are the same on both, so a vehicle resolved on one market can be used on the other.
> ReefAPI engine `autodoc` · 6 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/autodoc/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 /autodoc/v1/vehicle_lookup — 1 credit
Walk Autodoc's vehicle tree — this is where every other action starts, because car parts are only meaningful against a specific car. Call it with no parameters for the 52 makers; add `maker` for that maker's model families; add `model` for the generations (the chassis codes: E82, F20, W204); add `generation` for the actual engine variants, each with Autodoc's numeric vehicle id. Add `vehicle` (or pass a `vehicle_url`) and you get the car itself — its full factory description (engine, kW/hp, fuel, build years, engine code) plus every part category Autodoc stocks for it, ready to feed straight into `search`. Set `market` to `de` to walk the German storefront's tree instead; the slugs are the same on both, so a vehicle resolved on one market can be used on the other.

**Parameters:**
- `market` (enum, optional, default "uk") — Which Autodoc storefront to read: `uk` (autodoc.co.uk, prices in GBP) or `de` (autodoc.de, prices in EUR). Defaults to `uk`. This decides the catalogue, the prices and the currency, and it applies to EVERY action here — the vehicle tree, the fitment listing, the part page, both searches and the category list. The SLUGS are shared between the two storefronts, so a vehicle or category you resolved on one market can be passed straight to the other; the ids (`article_id`, `vehicle_id`, `category_id`) are the same part and the same car on both. What changes is the price, the currency and the language of the human-readable text. Currency is always read from the page itself and returned in `currency`, so you never have to infer it from this value. [one of: uk, de]
- `maker` (string, optional) — The car manufacturer, as the slug Autodoc uses in its own URLs — `bmw`, `vw`, `audi`, `mercedes-benz`, `ford`, `vauxhall`, `toyota`, `renault`. Call `vehicle_lookup` with no parameters to get the full list of the 52 makers with their exact slugs; never guess one. Autodoc publishes no open numeric id for a maker (that lives behind a route this API cannot reach), so the slug IS the key.
- `model` (string, optional) — The model family slug, as returned by `vehicle_lookup` for your maker — note Autodoc uses German slugs for some families even on the UK site (`1er-reihe` is the BMW 1 Series, `3er-reihe` the 3 Series). Requires `maker`.
- `generation` (string, optional) — The generation / body slug — the chassis code most owners know their car by (`1-coupe-e82`, `1-e87`, `1-f20`). Returned by `vehicle_lookup` for your model. Requires `maker` and `model`.
- `vehicle` (string, optional) — The exact engine variant — this is what makes a fitment answer true, and `search` REQUIRES it. Pass either the full slug `vehicle_lookup` returns (`12272-118-d`) or just Autodoc's numeric vehicle id (`12272`); both resolve to the same car. Requires `maker`, `model` and `generation`, because Autodoc's vehicle URLs are hierarchical — the id alone has no page of its own. NOTE: this is the ONLY way to scope parts to a car. Autodoc's `?carId=` query parameter looks like it should work and is silently ignored (measured: identical results with it, without it, and with a nonsense value).
- `vehicle_url` (string, optional) — A full Autodoc vehicle URL instead of the four path parameters — the shortest way to re-use a vehicle you already resolved. Accepted by both `vehicle_lookup` and `search`. Either storefront's spelling works (`autodoc.co.uk/spares/…` or `autodoc.de/ersatzteile/…`) and the URL's own domain SETS the market, so you do not have to pass `market` as well; passing a URL from one storefront together with `market` set to the other is refused rather than silently resolved to one of them.

**Returns:** level ('makers'|'models'|'generations'|'vehicles'|'vehicle') + items[] whose shape follows the level: makers/models/generations are {slug, name, url, path} (plus maker_id where Autodoc publishes one) and vehicles are {vehicle_id, vehicle_slug, engine_slug, name, url, path}. At the deepest level you additionally get vehicle{vehicle_id, vehicle_slug, engine_slug, name, description, maker, model, generation, url} and categories[]{slug, category_id, name, url, vehicle_scoped} — the part categories Autodoc stocks for that exact car. meta.pagination is absent here: Autodoc publishes each tree level whole, on one page. HONEST LIMITS: makers, models and generations have no numeric id in Autodoc's public HTML (those ids exist only behind a route this API cannot reach), so their SLUG is the identifier and `maker_id` is null unless the page happened to publish it. `vehicle_id` is always present and is Autodoc's own. A slug that does not exist is a typed NOT_FOUND, not an empty list — Autodoc answers a bad slug with HTTP 200 and the parent page, and this API checks the page's own canonical URL rather than reporting that as zero results.

### POST /autodoc/v1/search — 2 credits
The parts that FIT one exact car, in one part category — the question this API exists to answer. Give a `category` (from the `categories` action, or better from the `categories[]` that a `vehicle_lookup` on your vehicle returns) plus the full vehicle: `maker` + `model` + `generation` + `vehicle`, or a single `vehicle_url`. You get Autodoc's own fitment listing — every brand that makes that part for that car, with price, RRP, rating, review count, the manufacturer's part number, the key specifications and the image — 20 per page. THE VEHICLE IS NOT OPTIONAL, and that is Autodoc's data model rather than a limit of this API: a parts list only exists for one exact engine variant. Autodoc's category page, maker page, model page and generation page are all real pages that carry NO product listing whatsoever (measured: 0 parts at every level above the engine variant, 20 at it). Omitting the vehicle returns MISSING_PARAM rather than an empty result set that would cost you a call and tell you nothing. `market` decides which storefront is priced: `uk` (GBP) or `de` (EUR). The same vehicle and the same category work on both — what changes is the price, the currency and the language of the copy.

**Parameters:**
- `category` (string, required) — Which part category to list, as Autodoc's own category slug WITH its numeric id suffix — `brake-pad-set-10130`, `brake-disc-10132`, `air-filter-10360`, `shock-absorber-10175`. Call the `categories` action for all 199 with their ids. The number is the real identifier and the words are decoration: Autodoc resolves `anything-10130` to brake pads, so a slug whose words and id disagree will silently serve the id's category. A bare slug with no id (`brake-pad-set`) is accepted and redirected by Autodoc, and this API reports the category it actually landed on in `category.slug`.
- `maker` (string, optional) — The car manufacturer, as the slug Autodoc uses in its own URLs — `bmw`, `vw`, `audi`, `mercedes-benz`, `ford`, `vauxhall`, `toyota`, `renault`. Call `vehicle_lookup` with no parameters to get the full list of the 52 makers with their exact slugs; never guess one. Autodoc publishes no open numeric id for a maker (that lives behind a route this API cannot reach), so the slug IS the key.
- `model` (string, optional) — The model family slug, as returned by `vehicle_lookup` for your maker — note Autodoc uses German slugs for some families even on the UK site (`1er-reihe` is the BMW 1 Series, `3er-reihe` the 3 Series). Requires `maker`.
- `generation` (string, optional) — The generation / body slug — the chassis code most owners know their car by (`1-coupe-e82`, `1-e87`, `1-f20`). Returned by `vehicle_lookup` for your model. Requires `maker` and `model`.
- `vehicle` (string, optional) — The exact engine variant — this is what makes a fitment answer true, and `search` REQUIRES it. Pass either the full slug `vehicle_lookup` returns (`12272-118-d`) or just Autodoc's numeric vehicle id (`12272`); both resolve to the same car. Requires `maker`, `model` and `generation`, because Autodoc's vehicle URLs are hierarchical — the id alone has no page of its own. NOTE: this is the ONLY way to scope parts to a car. Autodoc's `?carId=` query parameter looks like it should work and is silently ignored (measured: identical results with it, without it, and with a nonsense value).
- `market` (enum, optional, default "uk") — Which Autodoc storefront to read: `uk` (autodoc.co.uk, prices in GBP) or `de` (autodoc.de, prices in EUR). Defaults to `uk`. This decides the catalogue, the prices and the currency, and it applies to EVERY action here — the vehicle tree, the fitment listing, the part page, both searches and the category list. The SLUGS are shared between the two storefronts, so a vehicle or category you resolved on one market can be passed straight to the other; the ids (`article_id`, `vehicle_id`, `category_id`) are the same part and the same car on both. What changes is the price, the currency and the language of the human-readable text. Currency is always read from the page itself and returned in `currency`, so you never have to infer it from this value. [one of: uk, de]
- `vehicle_url` (string, optional) — A full Autodoc vehicle URL instead of the four path parameters — the shortest way to re-use a vehicle you already resolved. Accepted by both `vehicle_lookup` and `search`. Either storefront's spelling works (`autodoc.co.uk/spares/…` or `autodoc.de/ersatzteile/…`) and the URL's own domain SETS the market, so you do not have to pass `market` as well; passing a URL from one storefront together with `market` set to the other is refused rather than silently resolved to one of them.
- `page` (integer, optional, default 1) — Which page of results to start from (1-based). Autodoc serves 20 parts per page. Page until `meta.pagination.has_more` is false.
- `max_results` (integer, optional, default 20) — How many parts to return in total (1-200). Anything above 20 costs one extra upstream page fetch per additional 20 results, and each Autodoc page is roughly 650KB, so ask for what you will use.

**Returns:** parts[]{article_id, article_number, url, title, brand, brand_slug, brand_id, category_name, category_generic_id, price, price_was, rrp, currency, on_sale, rating, rating_stars_out_of_10, review_count, image, images_360_url, specifications[]{name,value}, units_left, in_stock, fits_selected_vehicle} + category{slug, category_id, name, url} + vehicle{...} when you scoped to one. meta.fitment is always 'vehicle' (this action cannot run unscoped) and meta.scope echoes the four path segments that produced the answer, meta.pagination{page, per_page, returned, pages_fetched, has_more, next_page, known_pages_at_least} carries the counts. HONEST LIMITS: Autodoc publishes NO result total anywhere on the page — its pagination strip is a sliding window — so `total_results` is null and `known_pages_at_least` is a lower bound, never a total. `units_left` is Autodoc's own 'ending soon' counter and is null on most rows; it is not a warehouse stock figure and no such figure is published. `rating` is on a 0-5 scale for consistency with `product_detail`; Autodoc's own listing number is 0-10 and is returned verbatim beside it as `rating_stars_out_of_10`. There is no `sort` parameter because Autodoc's `?sort=` is a no-op (measured: identical price sequence with and without it) and publishing a filter that does not bite is worse than not having one.

**Example request body:**
```json
{
  "category": "brake-pad-set-10130",
  "maker": "bmw",
  "model": "1er-reihe",
  "generation": "1-coupe-e82",
  "vehicle": "12272-118-d",
  "max_results": 20
}
```

### POST /autodoc/v1/product_detail — 2 credits
One part in full, by Autodoc's numeric article id (or its URL). Returns the complete image gallery and the 360-degree photo set, the manufacturer's own specification table verbatim (fitting position, dimensions, material, WVA number, weight, EAN), the manufacturer part number, brand, price with currency, availability, shipping cost and return window, the aggregate rating and the customer reviews inline — and, the two fields that matter most in this industry, the full OE cross-reference (every vehicle-manufacturer part number this part replaces) and the equivalent parts Autodoc sells alongside it. `market` picks the storefront — the same `article_id` is the same part on both, priced in GBP on `uk` and EUR on `de`, and `currency` always reports what the page itself said.

**Parameters:**
- `article_id` (string, optional) — Autodoc's own numeric part id, exactly as every other action here returns it. This is the ONLY key `product_detail` needs — the brand in the URL is decoration and Autodoc redirects `/anything/7999106` to the right part. Provide `article_id` or `url`.
- `url` (string, optional) — A full Autodoc part URL — an alternative to `article_id`. Either storefront's domain works and the domain SETS the market, so `autodoc.de/ridex/7999106` returns the same part priced in EUR.
- `market` (enum, optional, default "uk") — Which Autodoc storefront to read: `uk` (autodoc.co.uk, prices in GBP) or `de` (autodoc.de, prices in EUR). Defaults to `uk`. This decides the catalogue, the prices and the currency, and it applies to EVERY action here — the vehicle tree, the fitment listing, the part page, both searches and the category list. The SLUGS are shared between the two storefronts, so a vehicle or category you resolved on one market can be passed straight to the other; the ids (`article_id`, `vehicle_id`, `category_id`) are the same part and the same car on both. What changes is the price, the currency and the language of the human-readable text. Currency is always read from the page itself and returned in `currency`, so you never have to infer it from this value. [one of: uk, de]
- `max_reviews` (integer, optional, default 25) — How many of the part's inline reviews to return (0-100). Autodoc publishes roughly 25 on the page itself; there is no second page of them on this route, so asking for more does not fetch more.

**Returns:** part{article_id, article_number, sku, url, title, subtitle, brand, category_name, breadcrumb[]{name,url}, description, price, currency, price_valid_until, availability, in_stock, item_condition, shipping_cost, shipping_currency, shipping_label, return_days, rating, rating_best, rating_count, reviews[]{author, text, rating, rating_best}, images[], image, image_count, images_360_url, specifications[]{name,value}, ean, weight_kg, oem_numbers[], oem_references[]{oem_number, vehicle_maker}, compatible_makers[]{maker_id, name}, fits_vehicles[], equivalent_parts[]{article_id, brand_id, category_id, category_generic_id, price, price_was, rating}}. meta.coverage reports which of the high-value fields this particular part carries, so you can see the completeness of the row you were given instead of trusting an average. FIELD NOTES: `ean` and `weight_kg` are lifted out of the manufacturer's spec table by name — they are rows in `specifications`, not separate fields at source, and null means the manufacturer supplied neither (about a third of parts). A car part has NO size/colour variant matrix; the equivalents of one are `oem_references` (what it replaces) and `equivalent_parts` (what replaces it), and both are returned. `compatible_makers` is the list of vehicle makers this part fits; the per-model expansion of that list is loaded by a route this API cannot reach, so it is deliberately absent rather than half-filled. Reviewer names are initials because that is how Autodoc publishes them. Autodoc publishes no stock quantity, and `in_stock` is its own In/OutOfStock flag.

**Example request body:**
```json
{
  "article_id": "7999106"
}
```

### POST /autodoc/v1/part_search — 2 credits
Autodoc's own catalogue-wide search, across roughly 6.7 million parts. Built for the lookup this trade actually does: paste a manufacturer part number (`1987474465`, `402B0071`) and get that part plus its alternatives, or type plain English (`brake pads`, `wiper blades`) for a normal keyword result set. Results are not scoped to a car — use `search` when you need fitment, and `oem_lookup` when the number you hold came off the vehicle manufacturer's own part. Set `market` to `de` to search the German storefront (EUR).

**Parameters:**
- `query` (string, required) — What to look for in Autodoc's own site search. This endpoint is built for the way the trade actually works: a MANUFACTURER PART NUMBER (`1987474465`, `402B0071`) returns that exact part plus the alternatives, an OE number often resolves too, and plain English (`brake pads`, `oil filter`) returns a normal keyword result set. Results are NOT scoped to a vehicle — this is the catalogue-wide search. To ask 'what fits MY car', use `search` with a vehicle instead. If you specifically have a VEHICLE MAKER's original number, `oem_lookup` is the better endpoint.
- `market` (enum, optional, default "uk") — Which Autodoc storefront to read: `uk` (autodoc.co.uk, prices in GBP) or `de` (autodoc.de, prices in EUR). Defaults to `uk`. This decides the catalogue, the prices and the currency, and it applies to EVERY action here — the vehicle tree, the fitment listing, the part page, both searches and the category list. The SLUGS are shared between the two storefronts, so a vehicle or category you resolved on one market can be passed straight to the other; the ids (`article_id`, `vehicle_id`, `category_id`) are the same part and the same car on both. What changes is the price, the currency and the language of the human-readable text. Currency is always read from the page itself and returned in `currency`, so you never have to infer it from this value. [one of: uk, de]
- `page` (integer, optional, default 1) — Which page of results to start from (1-based). Autodoc serves 20 parts per page. Page until `meta.pagination.has_more` is false.
- `max_results` (integer, optional, default 20) — How many parts to return in total (1-200). Anything above 20 costs one extra upstream page fetch per additional 20 results, and each Autodoc page is roughly 650KB, so ask for what you will use.

**Returns:** parts[] (the same card shape as `search`) + query{text, resolved_title}. meta.pagination as in `search`. HONEST LIMITS: no result total is published, so `total_results` is null. A term Autodoc does not carry returns HTTP 200 with zero parts and `meta.notes` saying so — an honest empty, not a failure. `search` and `part_search` return the SAME `article_id` for the same part, so results from either can be fed straight into `product_detail`.

**Example request body:**
```json
{
  "query": "1987474465"
}
```

### POST /autodoc/v1/oem_lookup — 3 credits
Cross-reference a vehicle manufacturer's ORIGINAL part number to everything Autodoc sells as a replacement for it. Give BMW's 34216774692 and get the Brembo, TRW, Bosch, ATE and budget-brand equivalents, each with price, rating and the manufacturer's own part number. This is the endpoint a workshop or a parts reseller uses, and it is the reverse of the `oem_numbers` list `product_detail` returns for a part. Set `market` to `de` for the German storefront's prices (EUR); the OE number and the parts it resolves to are the same on both.

**Parameters:**
- `oem_number` (string, required) — A vehicle manufacturer's ORIGINAL part number (BMW 34216774692, VAG 1K0615301AA). Returns every aftermarket part Autodoc sells as a replacement for it — the cross-reference this industry runs on. Spaces and dots are ignored. A number Autodoc does not carry returns a typed NOT_FOUND rather than the catalogue landing page it actually serves.
- `market` (enum, optional, default "uk") — Which Autodoc storefront to read: `uk` (autodoc.co.uk, prices in GBP) or `de` (autodoc.de, prices in EUR). Defaults to `uk`. This decides the catalogue, the prices and the currency, and it applies to EVERY action here — the vehicle tree, the fitment listing, the part page, both searches and the category list. The SLUGS are shared between the two storefronts, so a vehicle or category you resolved on one market can be passed straight to the other; the ids (`article_id`, `vehicle_id`, `category_id`) are the same part and the same car on both. What changes is the price, the currency and the language of the human-readable text. Currency is always read from the page itself and returned in `currency`, so you never have to infer it from this value. [one of: uk, de]
- `page` (integer, optional, default 1) — Which page of results to start from (1-based). Autodoc serves 20 parts per page. Page until `meta.pagination.has_more` is false.
- `max_results` (integer, optional, default 20) — How many parts to return in total (1-200). Anything above 20 costs one extra upstream page fetch per additional 20 results, and each Autodoc page is roughly 650KB, so ask for what you will use.

**Returns:** parts[] (the same card shape as `search`) + oem{number, normalised, resolved_title, url}. HONEST LIMITS: Autodoc answers an OE number it does not carry with HTTP 200 and its catalogue landing page — which would look like a working result set — so this action checks the page identity and returns a typed NOT_FOUND instead. Supplier part numbers do NOT resolve here (that is what `part_search` is for); only vehicle-manufacturer OE numbers do.

**Example request body:**
```json
{
  "oem_number": "34216774692"
}
```

### POST /autodoc/v1/categories — 1 credit
Autodoc's part-category vocabulary — all 199 leaf categories with the slug and numeric id the `search` action needs, so a caller never has to guess one. Optionally filter with `group` (`brake`, `filter`, `suspension`). `market` picks the storefront, which is what decides the LANGUAGE of the slugs and names — the numeric ids are the same category on both, and a uk slug passed to a de `search` resolves correctly because the number is what Autodoc keys on. Cached hard: this list barely moves.

**Parameters:**
- `group` (string, optional) — Optional substring filter over the category list (`brake`, `filter`, `suspension`, `oil`). Case-insensitive, matched against both the category name and its slug. Omit to get all 199. NOTE this matches the words of the storefront you asked for: on `market=de` the names and slugs are German, so `filter` still works (Ölfilter/Luftfilter) but `oil` returns nothing and `öl`/`brems` are what match (measured: `oil` -> 17 categories on uk, 0 on de). Filter on `category_id` instead when you need a market-independent answer — the ids are the same categories on both storefronts.
- `market` (enum, optional, default "uk") — Which Autodoc storefront to read: `uk` (autodoc.co.uk, prices in GBP) or `de` (autodoc.de, prices in EUR). Defaults to `uk`. This decides the catalogue, the prices and the currency, and it applies to EVERY action here — the vehicle tree, the fitment listing, the part page, both searches and the category list. The SLUGS are shared between the two storefronts, so a vehicle or category you resolved on one market can be passed straight to the other; the ids (`article_id`, `vehicle_id`, `category_id`) are the same part and the same car on both. What changes is the price, the currency and the language of the human-readable text. Currency is always read from the page itself and returned in `currency`, so you never have to infer it from this value. [one of: uk, de]

**Returns:** categories[]{slug, category_id, name, url}. NOTE these are the catalogue-wide categories. The set Autodoc actually stocks for one specific car is narrower and comes back from `vehicle_lookup` when you pass a vehicle — prefer that list when you have a car, because it cannot contain a category that car has no parts in.

**Example request body:**
```json
{
  "group": "brake"
}
```

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