License plate API for format validation
Tell a real registration from a typo, for every country and US state we cover. One call says whether a plate fits a format the authority actually issues, which series it belongs to, and how it should be written.
The VehiclesDB plates API checks a license plate number against the formats the issuing authority actually uses. You send a registration and a country or state code; it answers whether the plate is valid there, which registration series it belongs to (standard, motorcycle, diplomatic, dealer…), how the authority writes it, and, when it fails, why. It's for the form, the parking app, the fleet import or the ALPR pipeline that needs to know "is this a plate that can exist?" before trusting it.
What it covers
The plates API covers 127 jurisdictions and 1,409 registration series: 50 countries, plus the 56 US states and territories, 13 Canadian provinces and territories, and 8 Australian states and territories that issue their own plates. Every series carries its format, the years it was issued, the vehicles it's issued to and the colours its source states. You can browse all of them for free on the license plates pages.
Each format is recorded with its source (a regulation, the authority's own documents or, where those are silent, a flagged secondary source), and the sources are listed on that jurisdiction's page. Where a country has changed systems over the years, the old series are there too, with their dates, so an older car's plate still validates.
Check one plate
POST /api/v1/plates/{code}/match takes the registration in a JSON body. {code} is a country (es, de, nl) or a state or province (us-ca, ca-on, au-nsw), and it isn't case sensitive.
curl -X POST "https://vehiclesdb.com/api/v1/plates/es/match" \
-H "Authorization: Bearer vdb_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"plate": "1234 BCD"}'
A Spanish plate typed exactly as it's issued comes back like this:
{
"plate": "1234 BCD",
"jurisdiction": {
"code": "es",
"name": "Spain",
"country": "Spain",
"authority": "Dirección General de Tráfico (DGT) — Reglamento General de Vehículos, RD 2822/1998, Anexo XVIII",
"series_count": 19
},
"valid": true,
"exact": true,
"as_issued": null,
"strict": true,
"reason": "ok",
"excluded_letters": [],
"passenger": true,
"matches": [
{
"id": "es-national-2000",
"class": "standard",
"pattern": "9999 LLL",
"period": {
"start": 2000
},
"categories": [
"car",
"van",
"truck",
"bus",
"motorcycle"
],
"passenger": true,
"background": "#FFFFFF",
"foreground": "#000000",
"render_url": "https://vehiclesdb.com/plates/es/es-national-2000.svg",
"template_url": "https://vehiclesdb.com/plates/es/es-national-2000/template.svg"
}
],
"near_misses": [],
"attribution": {
"source": "VehiclesDB",
"url": "https://vehiclesdb.com",
"license": "Identity layer: CC-BY-4.0; enrichment/resolution: proprietary. Display requires visible credit — see https://vehiclesdb.com/legal/attribution",
"dataset_version": "2026.11.9"
}
}
The fields that matter most:
valid: the plate fits at least one format this place issues.exact: it was typed exactly as issued, separators included. When it wasn't,as_issuedgives you the authority's own spelling to store or show.matches: every series whose format accepts it. There's often more than one, because standard, export and historic series can share a pattern; each comes with its class, years, vehicle categories and colours.reason: one code that says why, when the answer is no:bad_shape(no series has that shape),excluded_letters(the shape exists but the authority never issues those letters, andexcluded_letterslists them) orunverifiable(only a catch-all series has the shape).passenger: a strict match is issued to a vehicle that carries people, not only to trailers or machinery. Handy for ride-sharing and rental forms.
Typed without the dashes
People type plates however they like. The matcher has two tiers: exact, the plate as the authority prints it, and lenient, where separators are forgiven but the letters and digits never are. Here's a Dutch plate typed as 12GBBD:
{
"plate": "12GBBD",
"jurisdiction": {
"code": "nl",
"name": "Netherlands",
"country": "Netherlands",
"authority": "RDW (Dienst Wegverkeer)",
"series_count": 33
},
"valid": true,
"exact": false,
"as_issued": "12-GB-BD",
"strict": true,
"reason": "ok",
"excluded_letters": [],
"passenger": true,
"matches": [
{
"id": "nl-sidecode6-car",
"class": "standard",
"pattern": "99-LL-LL",
"period": {
"start": 2000
},
"categories": [
"car"
],
"passenger": true,
"background": "#F9B700",
"foreground": "#000000",
"render_url": "https://vehiclesdb.com/plates/nl/nl-sidecode6-car.svg",
"template_url": "https://vehiclesdb.com/plates/nl/nl-sidecode6-car/template.svg"
},
{
"id": "nl-export",
"class": "export",
"pattern": "LLL-99-L",
"period": {
"start": 1951
},
"categories": [
"car",
"van",
"truck",
"bus",
"motorcycle",
"moped",
"trailer",
"semitrailer",
"tractor",
"machine"
],
"passenger": true,
"background": "#FFFFFF",
"foreground": "#000000",
"render_url": "https://vehiclesdb.com/plates/nl/nl-export.svg",
"template_url": "https://vehiclesdb.com/plates/nl/nl-export/template.svg"
},
{
"id": "nl-historic-darkblue-agricultural",
"class": "historic",
"pattern": "LLL-99-L",
"period": {
"start": 1978
},
"categories": [
"tractor",
"machine",
"trailer"
],
"passenger": false,
"background": "#1D2E5B",
"foreground": "#FFFFFF",
"render_url": "https://vehiclesdb.com/plates/nl/nl-historic-darkblue-agricultural.svg",
"template_url": "https://vehiclesdb.com/plates/nl/nl-historic-darkblue-agricultural/template.svg"
}
],
"near_misses": [],
"attribution": {
"source": "VehiclesDB",
"url": "https://vehiclesdb.com",
"license": "Identity layer: CC-BY-4.0; enrichment/resolution: proprietary. Display requires visible credit — see https://vehiclesdb.com/legal/attribution",
"dataset_version": "2026.11.9"
}
}
exact is false and as_issued is 12-GB-BD, the way the RDW writes it. Notice the three matches too: the same shape is a standard car plate, an export plate and a historic agricultural plate in the Netherlands, and the API tells you all three rather than guessing.
Get every format for a country or state
GET /api/v1/plates/{code} returns a jurisdiction's whole series list in one call: each series' pattern (L a letter, 9 a digit), the years it ran, its vehicle categories and its colours where the source states them (null where it doesn't; the dataset doesn't guess). Use it to build your own input masks, hints or offline checks. A shortened example for Germany:
{
"jurisdiction": {
"code": "de",
"name": "Germany",
"country": "Germany",
"authority": "Fahrzeug-Zulassungsverordnung (FZV) — Bundesministerium für Digitales und Verkehr",
"series_count": 13
},
"series": [
{
"id": "de-standard",
"class": "standard",
"pattern": "LL-LL 9999",
"period": {
"start": 1995
},
"categories": [
"car",
"van",
"truck",
"bus",
"motorcycle",
"trailer"
],
"passenger": true,
"background": "#FFFFFF",
"foreground": "#000000",
"render_url": "https://vehiclesdb.com/plates/de/de-standard.svg",
"template_url": "https://vehiclesdb.com/plates/de/de-standard/template.svg"
},
{
"id": "de-seasonal",
"class": "seasonal",
"pattern": "LL-LL 999",
"period": {
"start": 1997
},
"categories": [
"car",
"van",
"truck",
"bus",
"motorcycle",
"trailer"
],
"passenger": true,
"background": "#FFFFFF",
"foreground": "#000000",
"render_url": "https://vehiclesdb.com/plates/de/de-seasonal.svg",
"template_url": "https://vehiclesdb.com/plates/de/de-seasonal/template.svg"
}
],
"attribution": {
"source": "VehiclesDB",
"url": "https://vehiclesdb.com",
"license": "Identity layer: CC-BY-4.0; enrichment/resolution: proprietary. Display requires visible credit — see https://vehiclesdb.com/legal/attribution",
"dataset_version": "2026.11.9"
}
}
Plate images for your interface
Every series in a response carries a render_url, the plate's design as an SVG, and a template_url: the same design with an empty, fully described text area and no registration in it. The template is the privacy-safe one: you fill in your user's plate in your own page, so the registration never leaves your side. Plate images are priced per image; create an account to see the prices.
Start
- Create a free account and mint a private key (
vdb_sk_…) under Settings → API keys. Keep it on your server. - Send your first
POST /api/v1/plates/{code}/matchwith one of the examples above. - The full contract, every parameter and error code, is in the API reference, generated from the code that serves the requests.
Every account starts with 100 free credits and no card. A match costs 1 credit, a whole jurisdiction's format list 5 credits, and mistakes like an unknown country code are free. If you need more, the pricing page has the plans.
If you'd rather not call an API at all, the plate formats are in the open dataset on GitHub under CC BY 4.0, and the Ruby gem (gem "vehicles") runs the same matcher offline. For physical plate sizes there's a separate guide to license plate dimensions.
Questions people ask
Does this API tell me who owns a car, or read plates from photos?
Neither. It checks a registration you already have as text against the formats each authority issues. It holds no owner or vehicle records, and it doesn't do image recognition: if you run plate recognition (ALPR/ANPR), this is the step after it, to check the read is a plate that can exist.
What does a check cost?
A match costs 1 credit and a jurisdiction's whole format list costs 5 credits. Every account starts with 100 free credits, no card. An unknown country code or a missing plate is answered for free.
Is my users' plate data sent anywhere?
A registration is personal data, so the match endpoint takes it in the body of a POST, never in the URL, where CDN and proxy logs and browser history would keep it. For showing a user their own plate, the template URL returns the design with an empty text area, so you fill in the registration in your own page and it never leaves your side.
Can I use the formats offline?
Yes. The plate formats are part of the open VehiclesDB dataset (CC BY 4.0), and the Ruby gem ships the same two-tier matcher with no network calls.