{"openapi":"3.1.0","info":{"title":"CarAPI.io Vehicle Data API","version":"1.0.0","description":"CarAPI.io is a unified vehicle-data API covering several national vehicle\nregistries across the Nordics and the Netherlands. Look up a vehicle by its\nregistration plate or VIN through one consistent interface.\n\n## Get a key in one call\n`POST /v1/signup` — no body, no authentication, no signup form — answers `201` with a\nworking API key and 10 tokens (10 basic lookups). It is an ordinary key on an ordinary\naccount, not a sandbox: the same endpoints, responses and prices as any other, so what\nyou test is what you build on. It is built for AI agents evaluating the API for someone,\nand works just as well from a terminal.\n\nWorking for someone who already has a CarAPI.io account? Use that account's key\n(https://carapi.io/api-keys) rather than creating one: a key from `POST /v1/signup` is a\nnew, separate account with its own 10 tokens.\n\nTo keep the key, the account needs an e-mail address: `POST /v1/signup/email` with\n`{\"email\": \"...\"}`, authenticated with the key. That address gets one link; its owner\nconfirms it and chooses a password, the key stops expiring and the account gets the free\nplan's 100 tokens a month. Opening that mail is all the person has to do: signing up or\nlogging in at carapi.io with the same address only sends the link again. A key whose\naddress is never confirmed stops working 30 days after it was created\n(`401 KEY_EXPIRED`). New keys are limited to 3 per IP address a day\nand 10 in 30 days (`429 SIGNUP_LIMIT_REACHED`). Free lookups by accounts without a\nconfirmed address share a daily ceiling (`503 FREE_LIMIT_REACHED` until 00:00 UTC).\n\nReal plates to test with: `dk` `AP15244`, `no` `DN88050`, `se` `YYO555`, `nl` `79NFN3`.\n\n## Authentication\nEvery request must include your API key as a Bearer token:\n`Authorization: Bearer <API_KEY>` (`X-API-Key: <API_KEY>` is accepted too). Keys\nstart with `car_live_`. There is no sandbox: every key queries the live registries\nand spends real tokens. `GET /v1` needs no key and lists the endpoints.\n\n## The free demo\n`CARAPI-DEMO` in place of a plate — `GET /v1/{country}/reg/CARAPI-DEMO`, for any of the\nfour countries — answers with a sample record in that registry's real shape. It costs\n0 tokens, makes no registry call, and works with no key at all as well as before your\ne-mail address is confirmed. It\nstands in for a VIN too: `GET /v1/{country}/vin/CARAPI-DEMO` answers the same sample for\n`dk`, `no` and `se` (`nl` has no VIN lookup). The response is a real response in every\nrespect — `summary` included — plus `meta.demo: true`. On the extended endpoints the\ndemo answers `400 DEMO_BASIC_ONLY`, with one exception: Sweden's `/inspection`, which the\nreal endpoint derives from the base record, is derived from the sample the same way. The\nother extended endpoints need a real plate, and spend tokens.\n\n## Response envelope\nSuccessful responses use\n`{ \"status\": \"success\", \"data\": <record>, \"summary\": {...}, \"meta\": {...} }`.\n\n`data` is the vehicle record exactly as returned by the national registry, so\nfield names, language and nesting **vary by country**: Denmark and the Netherlands nest\nthe record under `data.data`, Norway and Sweden are flat, and values are the registry's\nown (`Benzin`, `Bensin`, `Grå`). It is registry passthrough and is **not** normalized\ninto a fixed cross-country schema.\n\n`summary` is the part that is: registration, make, model, VIN, first registration and\nfuel in **one shape for every country**, built from `data` on every response (see the\n`VehicleSummary` schema). It sits next to `data` and never replaces it — `data` remains\nthe source of truth — and nothing in it is guessed: what the registry did not send is\n`null`. It is on vehicle lookups (by plate and by VIN) and on `/complete`; `/inspection`\nand `/debt` do not carry it.\n\n## Token cost & pricing\nEach endpoint costs tokens. The costs shown in this spec are for the **Free**\nplan (vehicle by reg/VIN = 1 token, inspection/debt = 2 tokens,\ncomplete = 5 tokens). The **actual** amount charged is dynamic per plan — higher\nplans receive a per-token discount — and `meta.tokensUsed` always reflects the\nexact amount charged. Responses served from CarAPI's cache (`meta.cached: true`)\nare billed at the same rate as a live lookup: you pay for the vehicle data, not\nfor whether we happened to have it warm.\n\n## Availability\nCoverage differs by country and endpoint; combinations the upstream registry does\nnot provide return HTTP 503 and are **not billed**. Current coverage:\n- **Vehicle** (reg): `dk, no, se, nl`; **Vehicle** (VIN): `dk, no, se` (NL has no VIN lookup).\n- **Inspection**: `dk` (full history) and `se` (from the registry record). `no`/`nl`: 503.\n- **Debt** (liens / tinglysning): `dk`. `no`/`se`/`nl`: 503.\n- **Complete** (aggregated bundle): `dk` and `se`. `no`/`nl`: 503 (only the base lookup exists).\n  A bundle says whether it is whole: `meta.partial` and `meta.missing` name any slice the\n  registry failed to deliver.\n\nErrors on our side are HTTP 500 and are not billed either.\n\n## Dutch enrichment\nA Dutch registration lookup may return an extra top-level `nl_registry` block next to\n`data`, carrying inspection history, defects found at those inspections, recalls,\ntaxation and the full technical record from Dutch public-registry data. It is\n**additive and optional**: `data` is unchanged, and the block is simply absent when the\nenrichment is unavailable — check for the key rather than assuming it. See the\n`nl_registry` schema for the field list and for which source wins where the two views\ndisagree.\n\n## Calling from a browser\nBy default a key is **server-side only**: a request carrying an `Origin` header from a\nsite you have not listed is refused with **403 `ORIGIN_NOT_ALLOWED`**. Keep the key on\nyour own server and call the API from there — in a browser it is visible to every\nvisitor, and everything it spends is billed to you.\n\nIf you do need it in a page (a plate field on your own site, say), open up **one key**\nfor the exact origins it will be called from, on the API keys page. The domain list stops\nother websites from using a key that is on public display; it cannot stop a script,\nbecause anything that is not a browser can send whatever `Origin` it likes. Preflights\n(`OPTIONS`) are always answered — they carry no key, so the decision happens on the\nrequest itself.\n\n## Rate limits\nTwo independent per-minute limits apply — a request limit and a token limit —\nand both depend on your plan. You are throttled by whichever you hit first;\nexceeding either returns HTTP 429, each with its own `code` and its own headers:\n\n| | Request limit | Token limit |\n|---|---|---|\n| Counts | requests | tokens spent |\n| Applies | per API key | per account — all of its keys together |\n| Headers | `RateLimit-Policy`, `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (the IETF draft-6 set) | `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` |\n| Reset | `RateLimit-Reset`: seconds until the window resets | `X-RateLimit-Reset`: ISO-8601 timestamp |\n| When exceeded | `429 RATE_LIMIT_EXCEEDED` | `429 TOKEN_RATE_LIMIT_EXCEEDED` |\n\nBoth of those 429s carry `Retry-After` — whole seconds until the window resets — and so\ndoes `503 UPSTREAM_ERROR` (30 seconds). **Honour `Retry-After`** instead of retrying in a\nloop. `429 INSUFFICIENT_TOKENS` carries none: the balance is empty, and waiting out the\nminute will not refill it.\n\nA request that delivers nothing — a vehicle that does not exist, an endpoint the country\ndoes not offer, a registry failure — costs no tokens and gives its share of the token\nlimit back as well; its `X-RateLimit-Remaining` already shows the budget returned. The\nfree demo is not metered by the token limit, so it carries no `X-RateLimit-*` headers.\n","contact":{"name":"CarAPI.io","url":"https://carapi.io"}},"servers":[{"url":"https://api.carapi.io/v1","description":"Production (/api/v1 is a back-compat alias of /v1)"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Access","description":"A working API key in one call, without a signup form (`POST /v1/signup`), and the step\nthat hands its account to a person (`POST /v1/signup/email`).\n"},{"name":"Basic","description":"Basic vehicle information (1 token)."},{"name":"Extended","description":"Extended vehicle information (2 tokens). Inspection is live for `dk` and `se`;\ndebt (liens / tinglysning) is live for `dk`. Unavailable combinations return\nHTTP 503 and are not billed. These need a real plate or VIN: the demo identifier\nanswers `400 DEMO_BASIC_ONLY` here, except on Sweden's `/inspection`.\n"},{"name":"Bundle","description":"Combined \"complete\" payload (5 tokens) aggregated from several registry calls.\nAvailable for `dk` (vehicle + inspections + debt + emissions) and `se`\n(vehicle + status + origin + emissions). `no`/`nl` return HTTP 503. Needs a real\nplate or VIN: the demo identifier answers `400 DEMO_BASIC_ONLY`.\n"}],"paths":{"/{country}/reg/{registration}":{"get":{"operationId":"getVehicleByRegistration","tags":["Basic"],"summary":"Vehicle by registration plate (1 token, Free plan)","description":"Basic vehicle information looked up by registration plate. Returns the core\nregistry record (make, model, variant, year, fuel, weights and more —\nfields vary by country), and `summary`: the same few facts in one shape for\nevery country.\n\n**Live for `dk`, `no`, `se`, `nl`.** Dutch lookups may additionally return the\noptional `nl_registry` enrichment block (inspection history, defects, recalls,\ntaxation, technical record) alongside `data`.\n\n`CARAPI-DEMO` as the plate is the free demo: sample data, 0 tokens.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/registration"}],"responses":{"200":{"$ref":"#/components/responses/VehicleSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/vin/{vin}":{"get":{"operationId":"getVehicleByVin","tags":["Basic"],"summary":"Vehicle by VIN (1 token, Free plan)","description":"Basic vehicle information looked up by VIN. Returns the same record as the\nregistration endpoint, with the same `summary`.\n\n**Live for `dk`, `no`, `se`.** The Netherlands (`nl`) has no VIN endpoint\nand returns HTTP 503.\n\n`CARAPI-DEMO` as the VIN is the free demo for `dk`, `no` and `se`: the same sample\nrecord as the plate route, 0 tokens.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/vin"}],"responses":{"200":{"$ref":"#/components/responses/VehicleSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/reg/{registration}/inspection":{"get":{"operationId":"getInspectionByRegistration","tags":["Extended"],"summary":"Inspection history by registration plate (2 tokens, Free plan)","description":"Periodic inspection / MOT history. Live for **`dk`** (full inspection history)\nand **`se`** (inspection dates and results from the registry record).\n`no`/`nl` return HTTP 503 and are not billed.\n\n**Needs a real plate** for `dk`, and spends tokens: the demo plate answers\n`400 DEMO_BASIC_ONLY` here. The one exception is `se`, where the demo returns the\ninspection view derived from its sample record, for 0 tokens.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/registration"}],"responses":{"200":{"$ref":"#/components/responses/InspectionSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/vin/{vin}/inspection":{"get":{"operationId":"getInspectionByVin","tags":["Extended"],"summary":"Inspection history by VIN (2 tokens, Free plan)","description":"Periodic inspection / MOT history looked up by VIN. Live for **`dk`** and\n**`se`**; `no`/`nl` return HTTP 503 and are not billed.\n\n**Needs a real VIN** for `dk`, and spends tokens: the demo identifier answers\n`400 DEMO_BASIC_ONLY` here, except for `se`.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/vin"}],"responses":{"200":{"$ref":"#/components/responses/InspectionSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/reg/{registration}/debt":{"get":{"operationId":"getDebtByRegistration","tags":["Extended"],"summary":"Outstanding debt by registration plate (2 tokens, Free plan)","description":"Liens / financing registered against the vehicle (Danish *tinglysning*) —\nliabilities and notices. Live for **`dk`**; `no`/`se`/`nl` return HTTP 503\nand are not billed.\n\n**Needs a real plate**, and spends tokens: the demo plate answers\n`400 DEMO_BASIC_ONLY` here.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/registration"}],"responses":{"200":{"$ref":"#/components/responses/DebtSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/vin/{vin}/debt":{"get":{"operationId":"getDebtByVin","tags":["Extended"],"summary":"Outstanding debt by VIN (2 tokens, Free plan)","description":"Liens / financing (Danish *tinglysning*) looked up by VIN. Live for **`dk`**;\n`no`/`se`/`nl` return HTTP 503 and are not billed.\n\n**Needs a real VIN**, and spends tokens: the demo identifier answers\n`400 DEMO_BASIC_ONLY` here.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/vin"}],"responses":{"200":{"$ref":"#/components/responses/DebtSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/reg/{registration}/complete":{"get":{"operationId":"getCompleteByRegistration","tags":["Bundle"],"summary":"Complete bundle by registration plate (5 tokens, Free plan — DK & SE)","description":"A richer combined payload assembled from several registry calls in one request,\nfor 5 tokens instead of paying for each lookup separately:\n- **`dk`**: advanced vehicle record + inspections + debt (tinglysning) + emissions.\n- **`se`**: vehicle record + vehicle-status + vehicle-origin + emissions.\n\nEach section is best-effort — a slice the registry can't return comes back `null`\nrather than failing the whole call. `meta.partial` and `meta.missing` say which\n`null`s are failures: a slice with nothing to report (no registered debt) is a\ncomplete answer and is not listed. `no`/`nl` return HTTP 503 (only the base\nlookup exists there) and are not billed.\n\n**Needs a real plate**, and spends tokens: the demo plate answers\n`400 DEMO_BASIC_ONLY` here.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/registration"}],"responses":{"200":{"$ref":"#/components/responses/CompleteSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/{country}/vin/{vin}/complete":{"get":{"operationId":"getCompleteByVin","tags":["Bundle"],"summary":"Complete bundle by VIN (5 tokens, Free plan — DK & SE)","description":"The aggregated \"complete\" payload looked up by VIN. Available for **`dk`**\n(vehicle + inspections + debt + emissions) and **`se`** (vehicle + status +\norigin + emissions). `no`/`nl` return HTTP 503 and are not billed.\n\nThe plate-keyed slices (`dk` emissions; `se` status, origin and emissions) are\nlooked up by the plate the vehicle record carries. `meta.partial` and\n`meta.missing` work as on the plate route.\n\n**Needs a real VIN**, and spends tokens: the demo identifier answers\n`400 DEMO_BASIC_ONLY` here.\n","x-availability":"live","parameters":[{"$ref":"#/components/parameters/country"},{"$ref":"#/components/parameters/vin"}],"responses":{"200":{"$ref":"#/components/responses/CompleteSuccess"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/signup":{"get":{"operationId":"describeSignup","tags":["Access"],"summary":"What POST /v1/signup does (no key)","description":"Plain instructions, for a client that reads before it writes. Changes nothing.","security":[],"responses":{"200":{"description":"What the POST does, what it hands out and its limits.","content":{"application/json":{"schema":{"type":"object"},"examples":{"signupInfo":{"$ref":"#/components/examples/SignupInfoExample"}}}}},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createKey","tags":["Access"],"summary":"Get a working API key in one call (no key, no signup form)","description":"Creates an ordinary account without an e-mail address and answers with an ordinary\nAPI key holding 10 tokens — the same endpoints, responses and prices as any other key.\nSend no body. The key stops working 30 days after it was created unless the account\ngets an e-mail address that its owner confirms (`POST /v1/signup/email`).\n\nEvery call creates a new account. If the person you work for already has one, use its\nkey instead (https://carapi.io/api-keys).\n\nLimits: 3 new keys per IP address (IPv6: per /48) a day and 10 in 30 days (`429\nSIGNUP_LIMIT_REACHED`, with `Retry-After`). `503 FREE_LIMIT_REACHED` when today's free\nlookups for unconfirmed accounts are used up — a key handed out then could not make a\nsingle lookup — and `503 SIGNUP_UNAVAILABLE` while instant keys are switched off or\ntoday's keys for the whole service are handed out (with `Retry-After`). Calls from a\nweb page on another site are refused (`403 ORIGIN_NOT_ALLOWED`).\n","security":[],"responses":{"201":{"description":"A working key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"},"examples":{"signupCreated":{"$ref":"#/components/examples/SignupCreatedExample"}}}}},"403":{"description":"`HTTPS_REQUIRED` — the request came over plain http; a new key must not travel unencrypted. `ORIGIN_NOT_ALLOWED` — the request came from a web page on another site; keys are for programs, terminals and servers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"httpsRequired":{"$ref":"#/components/examples/SignupHttpsRequiredExample"}}}}},"429":{"description":"`SIGNUP_LIMIT_REACHED` — this IP address already has its 3 keys today or 10 in 30\ndays; `Retry-After` says when another is possible. Or `RATE_LIMIT_EXCEEDED`: more\nthan 10 requests a minute to this endpoint from one address.\n","headers":{"Retry-After":{"description":"Whole seconds until another key is possible.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"signupLimitReached":{"$ref":"#/components/examples/SignupLimitReachedExample"}}}}},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"`FREE_LIMIT_REACHED` (until 00:00 UTC; `Retry-After` in seconds) or\n`SIGNUP_UNAVAILABLE` (instant keys switched off; a person can sign up at\nhttps://carapi.io/signup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"freeLimitReached":{"$ref":"#/components/examples/FreeLimitReachedExample"},"signupUnavailable":{"$ref":"#/components/examples/SignupUnavailableExample"}}}}}}}},"/signup/email":{"post":{"operationId":"attachEmail","tags":["Access"],"summary":"Attach the e-mail address that keeps a key from POST /v1/signup","description":"Authenticated with the key `POST /v1/signup` handed out. The address gets one link;\nwhoever opens it confirms it and chooses a password, after which the key stops\nexpiring and the account gets the free plan's 100 tokens a month. The key keeps\nworking on its remaining tokens in the meantime (`tokensRemaining`). Within an hour of\nthe first address, sending another one replaces it (the earlier link stops working);\nafter that the address is fixed, so a key that leaks cannot move the account. Sending\nthe same address again mails a new link. At most 5 addresses an hour per key, 3 links\na day per account and 2 a day to one inbox (`+tags` count as the same inbox); addresses\nunder reserved domains (`.invalid`, `.test`, `.example`, `.localhost`) are refused.\n\nOpening the mail is all the person has to do. Signing up or logging in at carapi.io\nwith that address does not create a second account: it sends the link again. Whoever\ngets a mail they did not expect can stop all further ones to that address with the\nlink in it. Calls from a web page on another site are refused (`403\nORIGIN_NOT_ALLOWED`): keys are for programs, terminals and servers.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"The address of the person who will own the account."},"firstName":{"type":"string","maxLength":100},"lastName":{"type":"string","maxLength":100},"company":{"type":"string","maxLength":255},"locale":{"type":"string","description":"Language for the e-mail: da, de, en, fi, nl, no, pl or sv."}}}}}},"responses":{"202":{"description":"The link is on its way.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["success"]},"email":{"type":"string"},"message":{"type":"string"},"next":{"type":"string","description":"What to tell the person."},"tokensRemaining":{"type":["integer","null"],"description":"What the key has left until the address is confirmed."},"keyExpiresAt":{"type":["string","null"],"format":"date-time"}}},"examples":{"emailAttached":{"$ref":"#/components/examples/EmailAttachedExample"}}}}},"400":{"description":"`INVALID_INPUT` — no valid `email` in the JSON body; `fields` names what was wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"emailInvalid":{"$ref":"#/components/examples/EmailInvalidExample"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"`EMAIL_IN_USE` — that address already has a CarAPI.io account (its owner logs in and\ncreates a key there). `EMAIL_ALREADY_SET` — this key's account already has a\nconfirmed address of its own, or an address that can no longer be changed with the\nkey: another address is accepted only within an hour of the first one (sending the\nsame address again mails a new link). `EMAIL_DECLINED` — the owner of that address\nhas asked not to receive these e-mails. `EMAIL_UNDELIVERABLE` — mail to that address\nbounced, or its owner reported our mail as spam, so the link would never arrive.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"emailInUse":{"$ref":"#/components/examples/EmailInUseExample"},"emailAlreadySet":{"$ref":"#/components/examples/EmailAlreadySetExample"}}}}},"429":{"description":"`RATE_LIMIT_EXCEEDED` — more than 5 addresses in an hour for this key; or the account or that inbox already got the most links we send in a day (3 per account, 2 per inbox); or today's e-mails of this kind for the whole service are sent (`Retry-After` until 00:00 UTC). Nothing was changed, and any link already sent still works.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"emailRateLimit":{"$ref":"#/components/examples/EmailRateLimitExample"}}}}},"500":{"$ref":"#/components/responses/InternalError"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key as a Bearer token (the scheme is case-insensitive). Keys start with\n`car_live_`; there is no sandbox — every key spends real tokens.\n"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"The same API key, sent in an `X-API-Key` header instead."}},"parameters":{"country":{"name":"country","in":"path","required":true,"description":"Country code (case-insensitive).","schema":{"type":"string","enum":["dk","no","se","nl"]},"example":"dk"},"registration":{"name":"registration","in":"path","required":true,"description":"Registration plate. Spaces and dashes are optional and stripped, and case is\nignored, so `AB 12 345`, `ab-12345` and `AB12345` are the same plate.\n- dk: 2–7 letters/digits — standard `AB12345` or a personalised plate.\n- no: 2–7 letters/digits — `AB12345`, `AB1234` (motorcycle, trailer) or personalised.\n- se: 2–7 letters/digits — `ABC123`, the post-2019 `ABC12D`, or personalised.\n- nl: kenteken such as `12-ABC-3`, `AB-123-CD`, `1-ABC-23`.\n\nThese are formats, not vehicles: a well-formed plate that is not registered\nreturns 404 and is not billed.\n\n`CARAPI-DEMO` is the free demo lookup — sample data for every country, 0 tokens,\nand it works before your e-mail address is confirmed. It is the example below, so\n\"try it\" costs nothing; swap in a real plate when you are ready. It covers the basic\nlookup (and Sweden's `/inspection`); the other extended endpoints answer\n`400 DEMO_BASIC_ONLY` for it and need a real plate, which spends tokens.\n","schema":{"type":"string"},"example":"CARAPI-DEMO"},"vin":{"name":"vin","in":"path","required":true,"description":"17-character VIN (letters and numbers, excluding I, O and Q).\n\n`CARAPI-DEMO` is the free demo here too, for `dk`, `no` and `se`: the same sample\nrecord as the demo plate, 0 tokens. It is the example below, so \"try it\" costs\nnothing. As on the plate route it covers the basic lookup (and Sweden's\n`/inspection`); the other extended endpoints need a real VIN.\n","schema":{"anyOf":[{"type":"string","pattern":"^[A-HJ-NPR-Z0-9]{17}$"},{"type":"string","const":"CARAPI-DEMO"}]},"example":"CARAPI-DEMO"}},"schemas":{"SuccessMeta":{"type":"object","description":"Metadata describing the request. The free demo (`CARAPI-DEMO`) adds `demo: true` and a `notice` saying the record is sample data.","properties":{"tokensUsed":{"type":"integer","description":"Tokens charged for this request. 0 only on errors that are not billed (e.g. 503 for an unavailable country/endpoint).","example":1},"tokensRemaining":{"type":"integer","description":"Your remaining token balance.","example":2499},"responseTime":{"type":"string","description":"Server processing time.","example":"187ms"},"country":{"type":"string","description":"Country code, upper-cased.","example":"DK"},"cached":{"type":"boolean","description":"true when the response was served from CarAPI's cache. Cached responses are billed exactly like a live lookup.","example":false}},"required":["tokensUsed","tokensRemaining","responseTime","country","cached"]},"VehicleSummary":{"type":"object","description":"The facts every integration looks for, in **one shape for all four countries** —\nso a client does not have to know that Denmark and the Netherlands nest their record\nunder `data.data`, that Norway calls the make `make_name`, or that Sweden spells\npetrol \"Bensin\".\n\nBuilt from `data` on every response and never a replacement for it: `data` stays\nthe registry's record exactly as sent, and remains the source of truth. **Nothing\nhere is guessed** — a value the registry did not send is `null`, and a fuel term that\ndoes not map unambiguously keeps its original in `fuel.raw` with\n`fuel.normalized: null`.\n","properties":{"registration":{"type":["string","null"],"description":"The plate, as the registry writes it.","example":"AP15244"},"brand":{"type":["string","null"],"description":"The make, as the registry writes it (not re-cased).","example":"TOYOTA"},"model":{"type":["string","null"],"description":"The model, as the registry writes it. For Sweden it is the trade name, which the record keeps in `model2` (`XC60`, or `NISSAN QASHQAI` where the registry includes the make): Sweden's own `model` is a chassis code or just the make.","example":"AVENSIS"},"vin":{"type":["string","null"],"description":"The VIN. Always `null` for `nl` — the Dutch registry does not publish VINs.","example":"SB1EG76L50E099112"},"first_registration":{"type":"object","properties":{"date":{"type":["string","null"],"format":"date","description":"Date of first registration, `YYYY-MM-DD`. `null` when the registry sends no full date — Norway sends the year only.","example":"2014-07-23"},"year":{"type":["integer","null"],"description":"Year of first registration — from `date`, or the year Norway sends.","example":2014}},"required":["date","year"]},"fuel":{"type":"object","properties":{"normalized":{"type":["string","null"],"enum":["petrol","diesel","electric","hybrid","plugin_hybrid","lpg","cng","hydrogen","ethanol",null],"description":"The fuel on one vocabulary for every country. `null` when the registry's term does not map unambiguously (`LNG`; `Alcohol`, which may be ethanol or methanol; a bare `Gas`) and always for `no`, whose fuel is a numeric code that is passed through, never decoded.","example":"petrol"},"raw":{"type":["string","number","null"],"description":"The registry's own value, unchanged: `fuel_type` (dk, e.g. `Benzin`), `fuel_type_name` (se, e.g. `Bensin`), `fuel` (no, a numeric code), or the primary fuel in `nl_registry.technical.fuels` (nl — so `null` when that block is absent).","example":"Benzin"}},"required":["normalized","raw"]}},"required":["registration","brand","model","vin","first_registration","fuel"]},"VehicleResponse":{"type":"object","description":"Successful response envelope. `data` is the registry record as provided by\nthe national source — exact field names and nesting vary by country and are\nnot normalized. `summary` is the same few facts in one shape for every country.\n\nDutch registration lookups may carry an extra `nl_registry` block alongside\n`data`. It is additive: `data` keeps exactly the shape it has always had.\n","properties":{"status":{"type":"string","const":"success"},"data":{"type":"object","additionalProperties":true,"description":"The vehicle record exactly as returned by the national registry\n(passthrough). Field names and nesting vary by country: Denmark and the\nNetherlands nest the record under `data.data`, Norway and Sweden are flat.\nThe examples are representative; treat them as illustrative, not a\nguaranteed schema.\n"},"summary":{"description":"One shape for every country, built from `data` on every response. `null` only in the rare case that there was no record to read.","anyOf":[{"$ref":"#/components/schemas/VehicleSummary"},{"type":"null"}]},"nl_registry":{"$ref":"#/components/schemas/NlRegistry"},"meta":{"$ref":"#/components/schemas/SuccessMeta"}},"required":["status","data","summary","meta"]},"ExtendedResponse":{"type":"object","description":"Successful `/inspection` or `/debt` response. `data` is the registry's payload,\npassed through (Sweden's inspection view is derived from the vehicle record). There\nis no `summary` on these endpoints.\n","properties":{"status":{"type":"string","const":"success"},"data":{"type":"object","additionalProperties":true,"description":"The registry's payload, passed through. Fields vary by country."},"meta":{"$ref":"#/components/schemas/SuccessMeta"}},"required":["status","data","meta"]},"CompleteMeta":{"description":"`SuccessMeta`, plus whether the bundle is whole.","allOf":[{"$ref":"#/components/schemas/SuccessMeta"},{"type":"object","properties":{"partial":{"type":"boolean","description":"`true` when the registry failed to deliver at least one slice of the bundle. Billing is unchanged: a partial bundle costs the same as a whole one.","example":false},"missing":{"type":"array","description":"The slices the registry failed to deliver, e.g. `[\"emissions\"]`; empty when `partial` is `false`. A slice that is `null` because there is nothing to report (a car with no registered debt) is a complete answer and is not listed.","items":{"type":"string","enum":["inspections","debt","emissions","status","origin"]},"example":[]}},"required":["partial","missing"]}]},"CompleteResponse":{"type":"object","description":"Successful `/complete` response: the bundle in `data`, the vehicle's `summary`, and\n`meta.partial` / `meta.missing`.\n","properties":{"status":{"type":"string","const":"success"},"data":{"type":"object","additionalProperties":true,"description":"The bundle. `vehicle` is the base record exactly as the vehicle endpoint returns\nit (Denmark: nested under `vehicle.data`). Every other slice is the registry's\npayload, passed through, or `null` — `meta.missing` says which `null`s are\nfailures.\n- `dk`: `vehicle`, `inspections`, `debt`, `emissions`\n- `se`: `vehicle`, `status`, `origin`, `emissions`\n","properties":{"vehicle":{"type":"object","additionalProperties":true}},"required":["vehicle"]},"summary":{"description":"The vehicle's summary, built from `data.vehicle`.","anyOf":[{"$ref":"#/components/schemas/VehicleSummary"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/CompleteMeta"}},"required":["status","data","summary","meta"]},"NlRegistry":{"type":"object","description":"**Netherlands only, and optional.** A second, independent view of the vehicle\nbuilt from Dutch public-registry data: inspection history, defects recorded at\nthose inspections, recalls, taxation and the full technical record.\n\nIt is deliberately kept in its own namespace rather than merged into `data`,\nfor two reasons: `data` is the upstream record verbatim and its shape is a\nstable contract, and this block can be omitted whenever the enrichment is\nunavailable without changing anything else in the response. **Treat it as\noptional in your client** — check for the key rather than assuming it.\n\nWhere the two views disagree, the rule of thumb is: the registry view is\nfresher on inspection expiry, insurance, recalls, taxes and registration\ndates; `data` is authoritative on make/model naming, which is kept consistent\nwith the other supported countries.\n\nAbsent fields are **omitted**, never sent as `null`. All dates are\n`YYYY-MM-DD` and all timestamps are local Netherlands time without an offset.\n","properties":{"source":{"type":"string","enum":["mirror","live"],"description":"`mirror` = served from CarAPI's own nightly copy of the registry data (the normal case). `live` = fetched on demand because the vehicle was not in that copy yet.","example":"mirror"},"mirrored_at":{"type":"string","format":"date-time","description":"When CarAPI last refreshed this vehicle. Absent when `source` is `live`.","example":"2026-08-10T03:41:12Z"},"status":{"type":"object","description":"Registration status and the dates around it.","properties":{"apk_expiry_date":{"type":"string","format":"date","description":"Expiry of the periodic roadworthiness inspection (APK).","example":"2027-06-06"},"apk_expired":{"type":"boolean","description":"Whether `apk_expiry_date` is in the past (Netherlands local time).","example":false},"insured":{"type":"boolean","description":"Whether third-party liability insurance is registered.","example":true},"exported":{"type":"boolean","example":false},"taxi":{"type":"boolean","description":"Registered for use as a taxi.","example":false},"open_recall":{"type":"boolean","description":"An unresolved manufacturer recall is registered against the vehicle.","example":false},"transferable":{"type":"boolean","description":"Whether the registration can currently be transferred to a new keeper.","example":true},"odometer_judgement":{"type":"string","enum":["Plausible","Implausible","No judgement"],"description":"The registry's own verdict on whether the recorded mileage history is internally consistent. Omitted when no verdict has been recorded.","example":"Plausible"},"odometer_last_registration_year":{"type":"integer","example":2026},"first_admission_date":{"type":"string","format":"date","description":"First admission to traffic anywhere.","example":"2010-08-26"},"first_dutch_registration_date":{"type":"string","format":"date","description":"First registration in the Netherlands.","example":"2010-08-26"},"registration_date":{"type":"string","format":"date","description":"Start of the current keeper's registration.","example":"2018-06-19"},"imported":{"type":"boolean","description":"Derived: true when the vehicle was first registered in the Netherlands later than it was first admitted to traffic, i.e. a used import.","example":false}}},"technical":{"type":"object","description":"Technical record. Lengths are millimetres and masses kilograms throughout — note that the wheelbase and track width are converted from the centimetres the registry publishes.","properties":{"vehicle_type":{"type":"string","example":"Passenger car"},"body_type":{"type":"string","example":"Hatchback"},"body_type_eu":{"type":"string","example":"Hatchback"},"primary_colour":{"type":"string","example":"Grey"},"secondary_colour":{"type":"string"},"eu_vehicle_category":{"type":"string","example":"M1"},"energy_label":{"type":"string","description":"Energy-efficiency class, A to G.","example":"B"},"engine_displacement_cc":{"type":"integer","example":1349},"cylinders":{"type":"integer","example":4},"seats":{"type":"integer","example":5},"doors":{"type":"integer","example":4},"wheels":{"type":"integer","example":4},"mass_empty_kg":{"type":"integer","example":930},"mass_running_order_kg":{"type":"integer","example":1030},"mass_max_kg":{"type":"integer","example":1485},"mass_max_technical_kg":{"type":"integer","example":1485},"max_towable_braked_kg":{"type":"integer","example":900},"max_towable_unbraked_kg":{"type":"integer","example":500},"max_combination_mass_kg":{"type":"integer","example":2385},"wheelbase_mm":{"type":"integer","example":2490},"type_approval_number":{"type":"string","example":"e13*2001/116*0254*03"},"type_designation":{"type":"string","example":"DE"},"variant":{"type":"string","example":"14K"},"version":{"type":"string","example":"2"},"fuels":{"type":"array","description":"One entry per fuel — a hybrid or bi-fuel vehicle has several. Ordered by `sequence`; `sequence: 1` is the primary fuel.","items":{"type":"object","properties":{"sequence":{"type":"integer","example":1},"fuel_type":{"type":"string","example":"Petrol"},"consumption_combined_l_100km":{"type":"number","example":5.2},"co2_combined_g_km":{"type":"integer","example":125},"power_kw":{"type":"number","example":55},"emission_code":{"type":"string","example":"4"},"emission_class":{"type":"string","example":"70/220*2003/76B"},"exhaust_emission_level":{"type":"string","example":"EURO 4"},"sound_level_stationary_db":{"type":"integer","example":86},"sound_level_rpm":{"type":"integer","example":4500}}}},"axles":{"type":"array","items":{"type":"object","properties":{"axle_number":{"type":"integer","example":1},"axle_count":{"type":"integer","example":2},"track_width_mm":{"type":"integer","example":1480},"max_axle_load_kg":{"type":"integer","example":790},"tech_axle_load_kg":{"type":"integer","example":790}}}}}},"taxation":{"type":"object","description":"Values recorded at registration, in EUR.","properties":{"catalogue_price_eur":{"type":"number","description":"List price when new.","example":13484},"registration_tax_eur":{"type":"number","description":"Gross registration tax (BPM) levied on the vehicle.","example":1891}}},"inspections":{"type":"array","description":"Periodic inspection history, newest first. Note that the public source is a rolling window of roughly four years; older events are not published.","items":{"type":"object","properties":{"reported_at":{"type":"string","description":"Local Netherlands time, no offset.","example":"2026-06-03T10:58:00"},"expiry_date":{"type":"string","format":"date","example":"2027-06-06"},"inspection_type":{"type":"string","example":"Periodic inspection, light vehicles"},"notification_type":{"type":"string","example":"Periodic inspection"}}}},"defects":{"type":"array","description":"Defects recorded at those inspections, newest first.","items":{"type":"object","properties":{"code":{"type":"string","example":"524"},"description":{"type":"string","description":"English where a translation exists, otherwise the original Dutch — `description_language` says which.","example":"Dipped-beam pattern incorrect"},"description_language":{"type":"string","enum":["en","nl"],"example":"en"},"count":{"type":"integer","description":"Number of defects recorded at that inspection in total.","example":2},"reported_at":{"type":"string","example":"2026-06-03T10:58:00"}}}},"recalls":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string","example":"MGP170289"},"status_code":{"type":"string","enum":["open","repaired"],"example":"repaired"},"status":{"type":"string","example":"Manufacturer has reported the repair"}}}}}},"SignupResponse":{"type":"object","description":"A key from `POST /v1/signup`. Facts and the next step, written for a client that acts\non it and then tells a person what happened.\n","properties":{"status":{"type":"string","const":"success"},"apiKey":{"type":"string","description":"The key, shown once. Send it as `Authorization: Bearer <key>`.","example":"car_live_0123456789abcdef0123456789abcdef0123456789abcdef"},"tokensRemaining":{"type":"integer","example":10},"keyExpiresAt":{"type":"string","format":"date-time","description":"When the key stops working unless the account gets a confirmed e-mail address."},"account":{"type":"object","properties":{"email":{"type":["null","string"]},"emailConfirmed":{"type":"boolean"},"plan":{"type":"string","example":"free"}}},"about":{"type":"string"},"existingAccount":{"type":"string","description":"Use the existing account's key when there is one: every call creates a new account."},"examplePlates":{"type":"object","description":"Real plates the API answers for, one per country.","example":{"dk":"AP15244","no":"DN88050","se":"YYO555","nl":"79NFN3"}},"tryIt":{"type":"string","description":"A ready-to-run lookup with the new key."},"keepTheKey":{"type":"object","description":"What attaching an e-mail address does, and the call that does it.","properties":{"why":{"type":"string"},"method":{"type":"string","example":"POST"},"url":{"type":"string","format":"uri"},"headers":{"type":"object"},"body":{"type":"object"}}},"docs":{"type":"string","format":"uri"},"llms":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"},"pricing":{"type":"string","format":"uri"}}},"Error":{"type":"object","description":"Error envelope. Switch on the machine-readable `code`, not the message text.\nSome errors carry extra fields (e.g. `examples` on `INVALID_REGISTRATION`,\n`limit`/`plan` on `RATE_LIMIT_EXCEEDED`, `rateLimit` on\n`TOKEN_RATE_LIMIT_EXCEEDED`, `suggestion` on `UNKNOWN_ENDPOINT`, `tokensUsed: 0`\non 503).\n","properties":{"status":{"type":"string","const":"error"},"code":{"type":"string","description":"Machine-readable error code. Every error carries one."},"message":{"type":"string"},"requestId":{"type":"string","format":"uuid","description":"The request's id, also sent as the `X-Request-Id` header. Quote it when you ask us about a call."},"retryable":{"type":"boolean","description":"Whether trying the same request again can change the answer: true for the two\nrate limits, upstream failures and our own errors (wait for `Retry-After` when it\nis sent), false for everything the request itself must change.\n"},"docs":{"type":"string","format":"uri","description":"This code's entry in the documentation's error table."}},"required":["status","code","message"]}},"examples":{"VehicleSuccessExample":{"summary":"Vehicle found (DK — the record is nested under data.data)","value":{"status":"success","data":{"data":{"registration":"AP15244","vin":"SB1EG76L50E099112","brand":"TOYOTA","model":"AVENSIS","version":"1.8 VVT-i","fuel_type":"Benzin","model_year":2014,"first_registration_date":"2014-07-23","engine_displacement":1798,"engine_power":108,"total_weight":1975,"color":{"id":3,"name":"Grå"}}},"summary":{"registration":"AP15244","brand":"TOYOTA","model":"AVENSIS","vin":"SB1EG76L50E099112","first_registration":{"date":"2014-07-23","year":2014},"fuel":{"normalized":"petrol","raw":"Benzin"}},"meta":{"tokensUsed":1,"tokensRemaining":2499,"responseTime":"187ms","country":"DK","cached":false}}},"VehicleSuccessNoExample":{"summary":"Vehicle found (NO — flat, and the fuel is a numeric code)","value":{"status":"success","data":{"registration_number":"DN88050","vin":"WAUZZZ8K0DA039868","make_name":"AUDI","model":"A4 AVANT","first_registration_year":2012,"fuel":1},"summary":{"registration":"DN88050","brand":"AUDI","model":"A4 AVANT","vin":"WAUZZZ8K0DA039868","first_registration":{"date":null,"year":2012},"fuel":{"normalized":null,"raw":1}},"meta":{"tokensUsed":1,"tokensRemaining":2499,"responseTime":"204ms","country":"NO","cached":false}}},"VehicleSuccessSeExample":{"summary":"Vehicle found (SE — flat; the model people mean is in model2)","value":{"status":"success","data":{"registration":"YYO555","vin":"YV1UZ68BCJ1041333","brand":"VOLVO","model":"VOLVO","model2":"XC60","first_registration_date":"2017-11-28","fuel_type_name":"Diesel","last_inspection_date":"2025-11-18","next_inspection_date":"2027-01-31","last_inspection_result":"5","previous_inspection_date":"2024-10-22","previous_inspection_result":"5"},"summary":{"registration":"YYO555","brand":"VOLVO","model":"XC60","vin":"YV1UZ68BCJ1041333","first_registration":{"date":"2017-11-28","year":2017},"fuel":{"normalized":"diesel","raw":"Diesel"}},"meta":{"tokensUsed":1,"tokensRemaining":2499,"responseTime":"211ms","country":"SE","cached":false}}},"VehicleDemoExample":{"summary":"The free demo, CARAPI-DEMO (SE shown) — 0 tokens","value":{"status":"success","data":{"registration":"CARAPI-DEMO","vin":"YV1UZ68BCJ0000000","brand":"VOLVO","model":"VOLVO","model2":"XC60","first_registration_date":"2017-11-28","fuel_type_name":"Diesel","last_inspection_date":"2025-11-18","next_inspection_date":"2027-01-31","last_inspection_result":"5","previous_inspection_date":"2024-10-22","previous_inspection_result":"5"},"summary":{"registration":"CARAPI-DEMO","brand":"VOLVO","model":"XC60","vin":"YV1UZ68BCJ0000000","first_registration":{"date":"2017-11-28","year":2017},"fuel":{"normalized":"diesel","raw":"Diesel"}},"meta":{"tokensUsed":0,"tokensRemaining":10,"responseTime":"0ms","country":"SE","cached":false,"demo":true,"notice":"Sample data for a vehicle that does not exist. Swap in a real plate to query the registry."}}},"VehicleSuccessNlExample":{"summary":"Vehicle found (NL — nested, with the nl_registry enrichment)","value":{"status":"success","data":{"data":{"registration":"79NFN3","brand":"MAZDA","model":"MAZDA 2","type":"Personenauto","first_registration_date":"2010-08-26","engine_displacement":1349,"engine_power":55,"vehicle_weight":930,"doors":4,"seats":5}},"summary":{"registration":"79NFN3","brand":"MAZDA","model":"MAZDA 2","vin":null,"first_registration":{"date":"2010-08-26","year":2010},"fuel":{"normalized":"petrol","raw":"Petrol"}},"nl_registry":{"source":"mirror","mirrored_at":"2026-08-10T03:41:12Z","status":{"apk_expiry_date":"2027-06-06","apk_expired":false,"insured":true,"exported":false,"taxi":false,"open_recall":false,"transferable":true,"odometer_judgement":"Plausible","odometer_last_registration_year":2026,"first_admission_date":"2010-08-26","first_dutch_registration_date":"2010-08-26","registration_date":"2018-06-19","imported":false},"technical":{"vehicle_type":"Passenger car","body_type":"Hatchback","body_type_eu":"Hatchback","primary_colour":"Grey","eu_vehicle_category":"M1","energy_label":"B","engine_displacement_cc":1349,"cylinders":4,"seats":5,"doors":4,"wheels":4,"mass_empty_kg":930,"mass_running_order_kg":1030,"mass_max_kg":1485,"max_towable_braked_kg":900,"max_towable_unbraked_kg":500,"wheelbase_mm":2490,"type_approval_number":"e13*2001/116*0254*03","variant":"14K","fuels":[{"sequence":1,"fuel_type":"Petrol","consumption_combined_l_100km":5.2,"co2_combined_g_km":125,"power_kw":55,"exhaust_emission_level":"EURO 4","sound_level_stationary_db":86}],"axles":[{"axle_number":1,"axle_count":2,"track_width_mm":1480,"max_axle_load_kg":790},{"axle_number":2,"axle_count":2,"track_width_mm":1470,"max_axle_load_kg":750}]},"taxation":{"catalogue_price_eur":13484,"registration_tax_eur":1891},"inspections":[{"reported_at":"2026-06-03T10:58:00","expiry_date":"2027-06-06","inspection_type":"Periodic inspection, light vehicles","notification_type":"Periodic inspection"},{"reported_at":"2025-06-04T14:20:00","expiry_date":"2026-06-06","inspection_type":"Periodic inspection, light vehicles","notification_type":"Periodic inspection"}],"defects":[{"code":"524","description":"Dipped-beam pattern incorrect","description_language":"en","count":2,"reported_at":"2026-06-03T10:58:00"}],"recalls":[{"reference":"MGP170289","status_code":"repaired","status":"Manufacturer has reported the repair"}]},"meta":{"tokensUsed":1,"tokensRemaining":2498,"responseTime":"96ms","country":"NL","cached":false}}},"InspectionSeExample":{"summary":"Last inspection (SE — derived from the registry record)","value":{"status":"success","data":{"registration":"YYO555","vin":"YV1UZ68BCJ1041333","last_inspection_date":"2025-11-18","next_inspection_date":"2027-01-31","last_inspection_result":"5","previous_inspection_date":"2024-10-22","previous_inspection_result":"5"},"meta":{"tokensUsed":2,"tokensRemaining":2497,"responseTime":"214ms","country":"SE","cached":false}}},"CompletePartialExample":{"summary":"Complete bundle (DK) — two slices the registry failed to deliver","description":"`inspections` and `emissions` are `null` because the registry failed to deliver them, so they are listed in `meta.missing`. `debt` is `null` because no debt is registered against the car: a complete answer, and not listed.","value":{"status":"success","data":{"vehicle":{"data":{"registration":"AP15244","vin":"SB1EG76L50E099112","brand":"TOYOTA","model":"AVENSIS","version":"1.8 VVT-i","fuel_type":"Benzin","model_year":2014,"first_registration_date":"2014-07-23"}},"inspections":null,"debt":null,"emissions":null},"summary":{"registration":"AP15244","brand":"TOYOTA","model":"AVENSIS","vin":"SB1EG76L50E099112","first_registration":{"date":"2014-07-23","year":2014},"fuel":{"normalized":"petrol","raw":"Benzin"}},"meta":{"tokensUsed":5,"tokensRemaining":2495,"responseTime":"412ms","country":"DK","cached":false,"partial":true,"missing":["inspections","emissions"]}}},"InvalidRegistrationExample":{"summary":"Not a possible plate for the country","value":{"status":"error","code":"INVALID_REGISTRATION","message":"Invalid registration for SE. Swedish plates are 2–7 letters and digits, e.g. ABC123, ABC12D or a personalised plate. Spaces and dashes are ignored.","examples":["ABC123","ABC12D"]}},"InvalidVinExample":{"summary":"VIN malformed","value":{"status":"error","code":"INVALID_VIN","message":"VIN must be exactly 17 characters"}},"UnsupportedCountryExample":{"summary":"Country not supported","value":{"status":"error","code":"UNSUPPORTED_COUNTRY","message":"Country 'fr' is not supported. Supported countries: DK, NO, SE, NL","supportedCountries":["dk","no","se","nl"]}},"DemoBasicOnlyExample":{"summary":"The demo identifier on an extended endpoint (not billed)","value":{"status":"error","code":"DEMO_BASIC_ONLY","message":"The demo plate only covers the basic lookup. Call GET /v1/dk/reg/CARAPI-DEMO for it, or use a real plate for 'complete'.","tokensUsed":0}},"MissingApiKeyExample":{"summary":"No API key in the request","value":{"status":"error","code":"MISSING_API_KEY","message":"API key is required. Send it as \"Authorization: Bearer YOUR_API_KEY\" (or \"X-API-Key: YOUR_API_KEY\"). If the person you work for has a CarAPI.io account, use its key (https://carapi.io/api-keys). No key yet? Get one in one call, no signup form: POST https://api.carapi.io/v1/signup (10 free lookups). Sample data without any key: GET https://api.carapi.io/v1/se/reg/CARAPI-DEMO"}},"InvalidApiKeyExample":{"summary":"Invalid or revoked key","value":{"status":"error","code":"INVALID_API_KEY","message":"Invalid or revoked API key"}},"AccountBlockedExample":{"summary":"Account blocked by an administrator","value":{"status":"error","code":"ACCOUNT_BLOCKED","message":"This account has been blocked. Please contact support."}},"EmailNotVerifiedExample":{"summary":"The rest of the free allowance is still held back","value":{"status":"error","code":"EMAIL_NOT_VERIFIED","message":"Confirm your e-mail address to release your remaining 90 free tokens — check your inbox, or send the link again from https://carapi.io/dashboard. The demo lookup /v1/{country}/reg/CARAPI-DEMO works without tokens.","tokensRemaining":0}},"EmailNotVerifiedAgentExample":{"summary":"A key from POST /v1/signup has used its 10 tokens, and no address is attached yet","value":{"status":"error","code":"EMAIL_NOT_VERIFIED","message":"The 10 free lookups on this key are used. To get 90 more, free: POST https://api.carapi.io/v1/signup/email with {\"email\": \"<the address of the person you work for>\"}. We e-mail them one link; when they choose a password, this same key gets 90 more tokens and stops expiring. The demo lookup /v1/{country}/reg/CARAPI-DEMO works without tokens.","tokensRemaining":0}},"OriginNotAllowedExample":{"summary":"A browser call from an origin this key does not allow","value":{"status":"error","code":"ORIGIN_NOT_ALLOWED","message":"This key is server-side only. Call the API from your server, or enable browser access for a key at https://carapi.io/api-keys — the key is then visible to everyone who visits your site.","docs":"https://carapi.io/docs","tokensUsed":0}},"HttpsRequiredExample":{"summary":"A call over plain http","value":{"status":"error","code":"HTTPS_REQUIRED","message":"Plain http is not accepted: call https://api.carapi.io instead. This request sent your API key unencrypted, so replace the key at https://carapi.io/api-keys.","requestId":"3f1c2a9e-8b4d-4c1e-9a7f-2d5e6b8c0a14","retryable":false,"docs":"https://carapi.io/docs#error-HTTPS_REQUIRED","tokensUsed":0}},"NotFoundExample":{"summary":"Vehicle not found (not billed)","value":{"status":"error","code":"NOT_FOUND","message":"No vehicle found for DK AB12345.","tokensUsed":0}},"UnknownEndpointExample":{"summary":"The path is not an endpoint","value":{"status":"error","code":"UNKNOWN_ENDPOINT","message":"There is no endpoint at /v1/se/registration/ABC123. Did you mean /v1/se/reg/ABC123?","suggestion":"/v1/se/reg/ABC123","docs":"https://carapi.io/docs"}},"UpstreamErrorExample":{"summary":"The registry is failing or did not answer (not billed; Retry-After 30)","value":{"status":"error","code":"UPSTREAM_ERROR","message":"The vehicle registry is temporarily unavailable. Retry shortly.","tokensUsed":0}},"InternalErrorExample":{"summary":"An unexpected error on our side (not billed)","value":{"status":"error","code":"INTERNAL_ERROR","message":"An error occurred while fetching vehicle data","tokensUsed":0}},"RateLimitCheckFailedExample":{"summary":"The token limit could not be checked (refused, not billed)","value":{"status":"error","code":"RATE_LIMIT_CHECK_FAILED","message":"Rate limit check failed"}},"InsufficientTokensExample":{"summary":"Token balance too low (429, not 403)","value":{"status":"error","code":"INSUFFICIENT_TOKENS","message":"Insufficient tokens. Required: 1, Available: 0","tokensRequired":1,"tokensAvailable":0}},"RateLimitExceededExample":{"summary":"Per-minute request limit exceeded","value":{"status":"error","code":"RATE_LIMIT_EXCEEDED","message":"Rate limit exceeded. Your free plan allows 20 requests per minute.","limit":20,"plan":"free"}},"TokenRateLimitExceededExample":{"summary":"Per-minute token limit exceeded (balance is fine)","value":{"status":"error","code":"TOKEN_RATE_LIMIT_EXCEEDED","message":"Rate limit exceeded. Maximum 10 tokens per minute for free plan","rateLimit":{"limit":10,"used":10,"remaining":0,"resetAt":"2026-09-01T12:34:56.000Z"}}},"EndpointNotAvailableExample":{"summary":"Endpoint not available for this country","value":{"status":"error","code":"ENDPOINT_NOT_AVAILABLE","message":"'debt' data is not available for SE.","tokensUsed":0}},"CountryNotAvailableExample":{"summary":"Country supported but not yet configured","value":{"status":"error","code":"COUNTRY_NOT_AVAILABLE","message":"Vehicle data for 'NO' is not available. Supported countries: DK, NO, SE, NL.","tokensUsed":0}},"SignupInfoExample":{"summary":"What POST /v1/signup does","value":{"status":"success","howTo":"POST https://api.carapi.io/v1/signup with no body and no authentication answers 201 with a working API key.","example":"curl -X POST https://api.carapi.io/v1/signup","youGet":"An ordinary key with 10 tokens (10 basic lookups), the same endpoints and prices as any account. It works for 30 days; attach an e-mail address (POST https://api.carapi.io/v1/signup/email) that its owner confirms to keep it and get the free plan's 100 tokens a month.","existingAccount":"Working for someone who already has a CarAPI.io account? Use that account's key (https://carapi.io/api-keys) rather than creating one: a key from POST /v1/signup is a new, separate account with its own 10 tokens.","limits":"3 new keys per IP address a day, 10 in 30 days.","examplePlates":{"dk":"AP15244","no":"DN88050","se":"YYO555","nl":"79NFN3"},"docs":"https://carapi.io/llms.txt"}},"SignupCreatedExample":{"summary":"A working key","value":{"status":"success","apiKey":"car_live_0123456789abcdef0123456789abcdef0123456789abcdef","tokensRemaining":10,"keyExpiresAt":"2026-11-13T09:30:00.000Z","account":{"email":null,"emailConfirmed":false,"plan":"free"},"about":"An ordinary key on an ordinary account: the same endpoints, responses and prices as any other. A basic lookup costs 1 token; failed lookups (a plate that does not exist, a registry outage) cost nothing. https://api.carapi.io/v1/{country}/reg/CARAPI-DEMO answers free, in the real shape, for tests that need no real vehicle.","existingAccount":"Working for someone who already has a CarAPI.io account? Use that account's key (https://carapi.io/api-keys) rather than creating one: a key from POST /v1/signup is a new, separate account with its own 10 tokens.","examplePlates":{"dk":"AP15244","no":"DN88050","se":"YYO555","nl":"79NFN3"},"tryIt":"curl https://api.carapi.io/v1/se/reg/YYO555 -H \"Authorization: Bearer car_live_0123456789abcdef0123456789abcdef0123456789abcdef\"","keepTheKey":{"why":"The key stops working at keyExpiresAt unless the account gets an e-mail address that its owner confirms. Confirmed, the same key keeps working and the account gets the free plan's 100 tokens a month. We send the address one link; its owner chooses a password there.","method":"POST","url":"https://api.carapi.io/v1/signup/email","headers":{"Authorization":"Bearer <this key>","Content-Type":"application/json"},"body":{"email":"<the address of the person who will own the account>"}},"docs":"https://carapi.io/docs","llms":"https://carapi.io/llms.txt","openapi":"https://api.carapi.io/v1/openapi.json","pricing":"https://carapi.io/pricing"}},"SignupHttpsRequiredExample":{"summary":"A signup over plain http","value":{"status":"error","code":"HTTPS_REQUIRED","message":"Plain http is not accepted: call https://api.carapi.io/v1/signup. A new API key must not travel unencrypted.","requestId":"6a0b7c1d-2e3f-4a5b-8c9d-0e1f2a3b4c5d","retryable":false,"docs":"https://carapi.io/docs#error-HTTPS_REQUIRED"}},"SignupLimitReachedExample":{"summary":"This IP address has had its keys for today","value":{"status":"error","code":"SIGNUP_LIMIT_REACHED","message":"This IP address has already created 3 keys today, the most POST /v1/signup hands out. Use the key you already have, or have a person sign up at https://carapi.io/signup and create keys in the dashboard.","requestId":"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e","retryable":false,"docs":"https://carapi.io/docs#error-SIGNUP_LIMIT_REACHED"}},"FreeLimitReachedExample":{"summary":"Today's free lookups for unconfirmed accounts are used up","value":{"status":"error","code":"FREE_LIMIT_REACHED","message":"Today's 5000 free lookups for accounts without a confirmed e-mail address are used up; they reset at 00:00 UTC. Accounts with a confirmed address are not affected: confirm the address (POST /v1/signup/email for a key from POST /v1/signup) or sign up at https://carapi.io/signup.","requestId":"2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f","retryable":false,"docs":"https://carapi.io/docs#error-FREE_LIMIT_REACHED"}},"SignupUnavailableExample":{"summary":"Instant keys are switched off","value":{"status":"error","code":"SIGNUP_UNAVAILABLE","message":"Instant API keys are switched off for now. A person can sign up at https://carapi.io/signup and create keys in the dashboard.","requestId":"3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a","retryable":false,"docs":"https://carapi.io/docs#error-SIGNUP_UNAVAILABLE"}},"EmailAttachedExample":{"summary":"The link is on its way","value":{"status":"success","email":"anna@example.com","message":"We sent anna@example.com a link. When its owner opens it and chooses a password, this key stops expiring and the account gets the free plan's 100 tokens a month. Until then the key keeps working on the 7 tokens it has left.","next":"Tell the person to open the e-mail from CarAPI.io (it can land in spam) and choose a password. That is all: no separate signup is needed, and signing up or logging in at carapi.io with this address only sends the link again. The link works for 14 days; sending another address replaces this one.","tokensRemaining":7,"keyExpiresAt":"2026-11-13T09:30:00.000Z"}},"EmailInvalidExample":{"summary":"No valid address in the body","value":{"status":"error","code":"INVALID_INPUT","message":"Send JSON like {\"email\": \"name@example.com\"}: the address of the person who will own the account. firstName, lastName and company are optional.","fields":["email"],"requestId":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","retryable":false,"docs":"https://carapi.io/docs#error-INVALID_INPUT"}},"EmailInUseExample":{"summary":"The address already has an account","value":{"status":"error","code":"EMAIL_IN_USE","message":"That address already has a CarAPI.io account. Its owner can log in at https://carapi.io/login and create a key there, or send another address.","requestId":"5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c","retryable":false,"docs":"https://carapi.io/docs#error-EMAIL_IN_USE"}},"EmailAlreadySetExample":{"summary":"The key's account already has its own address","value":{"status":"error","code":"EMAIL_ALREADY_SET","message":"This key belongs to an account that already has its own confirmed e-mail address. Its owner can change the address in Settings at https://carapi.io/settings.","requestId":"6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d","retryable":false,"docs":"https://carapi.io/docs#error-EMAIL_ALREADY_SET"}},"EmailRateLimitExample":{"summary":"Too many addresses for this key in an hour","value":{"status":"error","code":"RATE_LIMIT_EXCEEDED","message":"Too many e-mail addresses sent for this key in an hour. Please try again later.","requestId":"7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e","retryable":true,"docs":"https://carapi.io/docs#error-RATE_LIMIT_EXCEEDED"}}},"responses":{"VehicleSuccess":{"description":"Vehicle record found.","headers":{"X-RateLimit-Limit":{"description":"Token limit: tokens per minute for your plan, per account. Absent on the free demo, which the token limit does not meter.","schema":{"type":"integer"},"example":10},"X-RateLimit-Remaining":{"description":"Token limit — tokens remaining in the current minute window.","schema":{"type":"integer"},"example":9},"X-RateLimit-Reset":{"description":"Token limit — ISO-8601 timestamp when the window resets.","schema":{"type":"string","format":"date-time"},"example":"2026-09-23T12:01:00.000Z"},"RateLimit-Policy":{"description":"Request limit, per API key: `<limit>;w=<window seconds>`.","schema":{"type":"string"},"example":"20;w=60"},"RateLimit-Limit":{"description":"Request limit — requests per minute for your plan, per API key.","schema":{"type":"integer"},"example":20},"RateLimit-Remaining":{"description":"Request limit — requests remaining in the current minute window.","schema":{"type":"integer"},"example":19},"RateLimit-Reset":{"description":"Request limit — seconds until the window resets.","schema":{"type":"integer"},"example":60}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VehicleResponse"},"examples":{"success":{"$ref":"#/components/examples/VehicleSuccessExample"},"successNo":{"$ref":"#/components/examples/VehicleSuccessNoExample"},"successSe":{"$ref":"#/components/examples/VehicleSuccessSeExample"},"successNl":{"$ref":"#/components/examples/VehicleSuccessNlExample"},"demo":{"$ref":"#/components/examples/VehicleDemoExample"}}}}},"InspectionSuccess":{"description":"Inspection data found","headers":{"X-RateLimit-Limit":{"description":"Token limit: tokens per minute for your plan, per account. Absent on the free demo, which the token limit does not meter.","schema":{"type":"integer"},"example":10},"X-RateLimit-Remaining":{"description":"Token limit — tokens remaining in the current minute window.","schema":{"type":"integer"},"example":9},"X-RateLimit-Reset":{"description":"Token limit — ISO-8601 timestamp when the window resets.","schema":{"type":"string","format":"date-time"},"example":"2026-09-23T12:01:00.000Z"},"RateLimit-Policy":{"description":"Request limit, per API key: `<limit>;w=<window seconds>`.","schema":{"type":"string"},"example":"20;w=60"},"RateLimit-Limit":{"description":"Request limit — requests per minute for your plan, per API key.","schema":{"type":"integer"},"example":20},"RateLimit-Remaining":{"description":"Request limit — requests remaining in the current minute window.","schema":{"type":"integer"},"example":19},"RateLimit-Reset":{"description":"Request limit — seconds until the window resets.","schema":{"type":"integer"},"example":60}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtendedResponse"},"examples":{"inspectionSe":{"$ref":"#/components/examples/InspectionSeExample"}}}}},"DebtSuccess":{"description":"Debt (lien) data found","headers":{"X-RateLimit-Limit":{"description":"Token limit: tokens per minute for your plan, per account. Absent on the free demo, which the token limit does not meter.","schema":{"type":"integer"},"example":10},"X-RateLimit-Remaining":{"description":"Token limit — tokens remaining in the current minute window.","schema":{"type":"integer"},"example":9},"X-RateLimit-Reset":{"description":"Token limit — ISO-8601 timestamp when the window resets.","schema":{"type":"string","format":"date-time"},"example":"2026-09-23T12:01:00.000Z"},"RateLimit-Policy":{"description":"Request limit, per API key: `<limit>;w=<window seconds>`.","schema":{"type":"string"},"example":"20;w=60"},"RateLimit-Limit":{"description":"Request limit — requests per minute for your plan, per API key.","schema":{"type":"integer"},"example":20},"RateLimit-Remaining":{"description":"Request limit — requests remaining in the current minute window.","schema":{"type":"integer"},"example":19},"RateLimit-Reset":{"description":"Request limit — seconds until the window resets.","schema":{"type":"integer"},"example":60}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtendedResponse"}}}},"CompleteSuccess":{"description":"Bundle assembled","headers":{"X-RateLimit-Limit":{"description":"Token limit: tokens per minute for your plan, per account. Absent on the free demo, which the token limit does not meter.","schema":{"type":"integer"},"example":10},"X-RateLimit-Remaining":{"description":"Token limit — tokens remaining in the current minute window.","schema":{"type":"integer"},"example":9},"X-RateLimit-Reset":{"description":"Token limit — ISO-8601 timestamp when the window resets.","schema":{"type":"string","format":"date-time"},"example":"2026-09-23T12:01:00.000Z"},"RateLimit-Policy":{"description":"Request limit, per API key: `<limit>;w=<window seconds>`.","schema":{"type":"string"},"example":"20;w=60"},"RateLimit-Limit":{"description":"Request limit — requests per minute for your plan, per API key.","schema":{"type":"integer"},"example":20},"RateLimit-Remaining":{"description":"Request limit — requests remaining in the current minute window.","schema":{"type":"integer"},"example":19},"RateLimit-Reset":{"description":"Request limit — seconds until the window resets.","schema":{"type":"integer"},"example":60}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteResponse"},"examples":{"partial":{"$ref":"#/components/examples/CompletePartialExample"}}}}},"BadRequest":{"description":"Invalid parameters. `code` is one of `INVALID_INPUT`, `UNSUPPORTED_COUNTRY`,\n`INVALID_REGISTRATION` (with an `examples` array) or `INVALID_VIN` — or\n`DEMO_BASIC_ONLY`: the demo identifier on an extended endpoint it does not cover,\nwhich needs a real plate or VIN.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalidRegistration":{"$ref":"#/components/examples/InvalidRegistrationExample"},"invalidVin":{"$ref":"#/components/examples/InvalidVinExample"},"unsupportedCountry":{"$ref":"#/components/examples/UnsupportedCountryExample"},"demoBasicOnly":{"$ref":"#/components/examples/DemoBasicOnlyExample"}}}}},"Unauthorized":{"description":"Authentication failed. `code` is `MISSING_API_KEY` (no key in an\n`Authorization: Bearer` or `X-API-Key` header — the message says how to get one:\n`POST /v1/signup`), `INVALID_API_KEY` (invalid or revoked), `KEY_EXPIRED` (a key\nfrom `POST /v1/signup` whose account never got a confirmed e-mail address, 30 days\nafter it was created — get a new one) or `USER_NOT_FOUND`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missingApiKey":{"$ref":"#/components/examples/MissingApiKeyExample"},"invalidApiKey":{"$ref":"#/components/examples/InvalidApiKeyExample"}}}}},"Forbidden":{"description":"The key authenticated, but this request may not proceed. `code` is one of:\n- `ACCOUNT_BLOCKED` — the account is blocked. The token balance is untouched and\n  access is restored as soon as the block is lifted.\n- `EMAIL_NOT_VERIFIED` — the free allowance is still held back. Confirm the address\n  to release it; the demo lookup works meanwhile. For a key from `POST /v1/signup`\n  the message says what the caller can do: attach the person's address\n  (`POST /v1/signup/email`), or send it again.\n- `ORIGIN_NOT_ALLOWED` — a browser on an origin this key does not allow. Call from\n  your server, or list the origin for that key. See \"Calling from a browser\".\n- `HTTPS_REQUIRED` — the request came over plain http, so the key crossed the network\n  unencrypted. Call `https://api.carapi.io`, and replace the key.\n- `NO_ACTIVE_SUBSCRIPTION` — the account has no subscription row to bill against. Not\n  something a request can fix; contact us.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"accountBlocked":{"$ref":"#/components/examples/AccountBlockedExample"},"emailNotVerified":{"$ref":"#/components/examples/EmailNotVerifiedExample"},"emailNotVerifiedAgentKey":{"$ref":"#/components/examples/EmailNotVerifiedAgentExample"},"originNotAllowed":{"$ref":"#/components/examples/OriginNotAllowedExample"},"httpsRequired":{"$ref":"#/components/examples/HttpsRequiredExample"}}}}},"NotFound":{"description":"Two different 404s, told apart by `code`:\n- `NOT_FOUND` — the registry answered and has no vehicle for this plate or VIN.\n  Not billed.\n- `UNKNOWN_ENDPOINT` — the path is not one of the endpoints (e.g.\n  `/v1/se/registration/…`). When the path is a recognisable near miss, the\n  response carries a `suggestion` with the endpoint that was probably meant.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"notFound":{"$ref":"#/components/examples/NotFoundExample"},"unknownEndpoint":{"$ref":"#/components/examples/UnknownEndpointExample"}}}}},"TooManyRequests":{"description":"Rate limit or token balance exceeded (HTTP 429 — note: insufficient tokens\nis 429, not 403). Three distinct conditions share the status, and every one\nof them carries a `code`:\n- `RATE_LIMIT_EXCEEDED` — the per-minute **request** limit, with `limit` and\n  `plan`. Back off.\n- `TOKEN_RATE_LIMIT_EXCEEDED` — the per-minute **token** limit, with a\n  `rateLimit` object (`limit`, `used`, `remaining`, `resetAt`). The balance is\n  fine; the spend rate is not. One call can cost 5 tokens, so this trips at a\n  request rate well below the limit above — spend more slowly, use cheaper\n  endpoints, or move up a plan. `resetAt` says when the window clears.\n- `INSUFFICIENT_TOKENS` — the balance is too low for this call. Waiting does\n  not help; top up or wait for the monthly reset. Carries `tokensRequired` and\n  `tokensAvailable`, or `tokensRemaining` when the pre-flight check caught it.\n\nThe two rate limits say when to come back: both carry `Retry-After`, and each\ncarries its own header family — `RateLimit-*` on `RATE_LIMIT_EXCEEDED`, and the\n`X-RateLimit-*` set (alongside `RateLimit-*`) on `TOKEN_RATE_LIMIT_EXCEEDED`.\n`INSUFFICIENT_TOKENS` carries no `Retry-After`.\n","headers":{"Retry-After":{"description":"Whole seconds until the exceeded window resets; wait at least this long before retrying. On `RATE_LIMIT_EXCEEDED` and `TOKEN_RATE_LIMIT_EXCEEDED`, never on `INSUFFICIENT_TOKENS`.","schema":{"type":"integer","minimum":1},"example":42},"RateLimit-Policy":{"description":"Request limit, per API key: `<limit>;w=<window seconds>`.","schema":{"type":"string"},"example":"20;w=60"},"RateLimit-Limit":{"description":"Request limit — requests per minute for your plan, per API key.","schema":{"type":"integer"},"example":20},"RateLimit-Remaining":{"description":"Request limit — requests remaining in the current minute window.","schema":{"type":"integer"},"example":0},"RateLimit-Reset":{"description":"Request limit — seconds until the window resets.","schema":{"type":"integer"},"example":42},"X-RateLimit-Limit":{"description":"Token limit (on `TOKEN_RATE_LIMIT_EXCEEDED`) — tokens per minute for your plan, per account.","schema":{"type":"integer"},"example":10},"X-RateLimit-Remaining":{"description":"Token limit (on `TOKEN_RATE_LIMIT_EXCEEDED`) — tokens left in the window, fewer than this call costs. Same value as `rateLimit.remaining`.","schema":{"type":"integer"},"example":0},"X-RateLimit-Reset":{"description":"Token limit (on `TOKEN_RATE_LIMIT_EXCEEDED`) — ISO-8601 timestamp when the window resets. Same value as `rateLimit.resetAt`.","schema":{"type":"string","format":"date-time"},"example":"2026-09-01T12:34:56.000Z"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rateLimitExceeded":{"$ref":"#/components/examples/RateLimitExceededExample"},"tokenRateLimitExceeded":{"$ref":"#/components/examples/TokenRateLimitExceededExample"},"insufficientTokens":{"$ref":"#/components/examples/InsufficientTokensExample"}}}}},"InternalError":{"description":"Something failed on our side (HTTP 500), and it is **not billed**: a lookup that\nhad already been charged is reversed, and `tokensUsed` — present when the lookup\nitself failed — reports what it cost you. `code` is one of:\n- `INTERNAL_ERROR` — an unexpected error while handling the request.\n- `AUTH_ERROR` — the API key could not be checked. (A wrong key is a 401.)\n- `RATE_LIMIT_CHECK_FAILED` — the token limit could not be checked, so the request\n  was refused rather than let through unmetered.\n\nAll three are temporary; retry after a short back-off.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internalError":{"$ref":"#/components/examples/InternalErrorExample"},"rateLimitCheckFailed":{"$ref":"#/components/examples/RateLimitCheckFailedExample"}}}}},"Unavailable":{"description":"The data could not be delivered (HTTP 503), and these responses are **not\nbilled** (`tokensUsed: 0`). `code` is one of:\n- `UPSTREAM_ERROR` — the national registry failed, throttled us, or did not\n  answer in time. Temporary: retry after the `Retry-After` it carries (30 seconds).\n- `ENDPOINT_NOT_AVAILABLE` — the country is supported but this particular\n  endpoint isn't offered for it (e.g. `debt` for `se`). Permanent for that\n  combination; the per-country coverage is in the table above.\n- `COUNTRY_NOT_AVAILABLE` — reserved, and unreachable in practice. All four\n  countries are live on one upstream key; there are no per-country keys to\n  set. An unknown country code is rejected earlier, with **400\n  `UNSUPPORTED_COUNTRY`**, before the handler that could raise this runs.\n- `PROVIDER_NOT_CONFIGURED` — our upstream credentials are missing (a deployment\n  fault on our side, which raises an operator alarm). Temporary: retry later.\n- `FREE_LIMIT_REACHED` — today's free lookups for accounts **without a confirmed\n  e-mail address** are used up (one ceiling shared by all of them). It resets at\n  00:00 UTC (`Retry-After` says how far away that is). Accounts with a confirmed\n  address and paid plans are not affected: confirm the address.\n","headers":{"Retry-After":{"description":"On `UPSTREAM_ERROR`: seconds to wait before retrying (30). On `FREE_LIMIT_REACHED`: seconds until 00:00 UTC. Absent on `ENDPOINT_NOT_AVAILABLE`, which retrying will not change.","schema":{"type":"integer","minimum":1},"example":30}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstreamError":{"$ref":"#/components/examples/UpstreamErrorExample"},"endpointNotAvailable":{"$ref":"#/components/examples/EndpointNotAvailableExample"},"countryNotAvailable":{"$ref":"#/components/examples/CountryNotAvailableExample"}}}}}}}}