Vehicle Database
What this is for
The vehicle database has two uses. First, make / model / year dropdowns: a parts store can let a customer pick their vehicle without a plate number, narrowing from make down to an exact variant. Second, resolving a plate lookup: every vehicle_id returned by /api/v1/lookup can be fetched here as a full record (series, engine, variant, year range). The database holds 23,000+ Australian market vehicle variants across 225+ makes (125,000+ year-level records), cars to trucks and buses.
NEWMotorcycles have their own cascade: add type=motorcycle to any /api/v1/vehicles request for 15,000+ motorcycles, scooters, ATVs and snowmobiles across 420+ makes (Honda, Yamaha, Kawasaki, Suzuki, Harley Davidson, KTM, Polaris ...). Without type, or with type=car, you get the car cascade, unchanged. Same seven levels, same rules and the same kind of vehicle_id: a motorcycle plate lookup returns the same ID as the motorcycle cascade, and /api/v1/vehicles/{vehicle_id} returns either kind, with vehicle_type saying which. Some older bikes have no year information: they have an ID and a full record with years empty and lowest_year null, but do not appear in the year lists.
Available on Starter plans and above (not Free or Lite). Vehicle database requests do not count toward your monthly lookup quota and have their own limit of 500 requests per minute.
Quick start in 60 seconds
Replace pk_live_YOUR_API_KEY with a Starter-or-above key and run these three commands in order.
1. List every make:
curl -s "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"data": [
{
"type": "make",
"data": ["ABARTH", "ACE EV", "ACURA", "ALEXANDER DENNIS", "ALFA ROMEO", "ALPINE", "..."]
}
],
"total": 227,
"duration_ms": 19.1
}2. Pick a make from that list and ask for its models:
curl -s "https://api.plateapi.com.au/api/v1/vehicles?make=TOYOTA" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"data": [
{
"type": "model",
"data": ["4 RUNNER", "4 RUNNER DELUXE", "4 RUNNER RV6, V6 SR5", "4 RUNNER SR5", "86 GT", "86 GT, GTS", "..."]
}
],
"total": 328,
"duration_ms": 4.67
}3. Fetch one full vehicle record by ID. 1380801 is the vehicle returned by the sandbox plate TEST123:
curl -s "https://api.plateapi.com.au/api/v1/vehicles/1380801" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"vehicle": {
"vehicle_id": 1380801,
"make": "TOYOTA",
"model": "HILUX SR",
"year_range": "2015-2020",
"series": "GUN126R -GUN126",
"engine": "2.8L, DIE 1GDFTV I4 16v DOHC I/C Turbo CRD {130kW}",
"variant": "SR, 4D Ute -, 4WD MR0KA3CD [THAILAND], AT/MT",
"detail": "full",
...
},
"duration_ms": 1.61
}How the cascade works
One endpoint, seven levels. Each request adds one more parameter and gets back the list for the next level. Every successful response has the same shape: {"success": true, "data": [{"type": "<level>", "data": [...]}], "total": N, "duration_ms": x}. The type field tells you which level you got.
If the vehicle database is briefly unavailable, any level returns 503 with a Retry-After header and {"success": false, "code": "unavailable", "data": [], "total": 0}, never a 200. So a 200 with an empty list always means a value that does not exist, not an outage.
| Level | Parameters you send | type returned | data contains | Example value |
|---|---|---|---|---|
| 1 | none | make | Makes | TOYOTA |
| 2 | make | model | Models for the make | HILUX |
| 3 | + model | year | Years, newest first (numbers) | 2019 |
| 4 | + year | series | Series / chassis codes | GUN126R -GUN126 |
| 5 | + series | engine | Engine descriptions | 2.8L, ... {130kW} |
| 6 | + engine | variant | Variant names (select_text) | 4D Ute -, ... AT/MT |
| 7 | + variant | vehicle | Vehicle records with vehicle_id | 1346446 |
Four rules cover almost every integration problem:
- Pass values exactly as returned, and URL-encode them. Values contain commas, double spaces, braces, brackets and slashes. Copy the string from the previous response without trimming or tidying it. Matching is exact, including letter case:
make=toyotaormodel=Hiluxreturns an empty list, and so does a double space collapsed to a single space. Neither is an error. Let your HTTP library do the encoding (curl --data-urlencode,URLSearchParams,requestsparams=). - An empty-string series is a real value. Some vehicles have no series code, so the series list contains
"". Select it like any other value and sendseries=(empty) to continue to engines. - Unknown values are not errors. A make, model or other value that does not exist returns
200with an emptydatalist. If you get an empty list, check that you passed the previous response's value unchanged. - Parameters build on each other, in order. A later parameter without the earlier ones is ignored: sending
modelwithoutmakereturns the makes list,yearwithoutmodelreturns the models.yearmust be a whole number between 1900 and 2100 and text values are limited to 200 characters; anything else returns400.
Every list contains only vehicles that have a full database record, so any path that reaches level 7 returns at least one vehicle with a vehicle_id. You can stop at any level: a make/model/year picker only needs levels 1 to 3.
Level 7 usually returns exactly one vehicle, but treat it as a list. Fewer than 100 paths return two: different vehicles whose records overlap in the year you picked and share every text value. For example KIA / SELTOS / 2022 / EP81A / 2.0L ... CVT returns 5434441 (2019-2022) and 2573335 (2022 on). Show both, or fetch each record by ID and use its year_range to tell them apart.
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Required | An API key on a Starter plan or above. Free and Lite keys receive 403 on this endpoint. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
type NEW | string | Optional | car (default) or motorcycle: which cascade to query. Any other value returns 400. |
make | string | Optional | Manufacturer name exactly as returned by the makes list (e.g. TOYOTA) |
model | string | Optional | Model name exactly as returned by the models list. Requires make. |
year | integer | Optional | Model year as returned by the years list. Requires make and model. |
series | string | Optional | Series/chassis code as returned by the series list. Can be an empty string for vehicles without a series code. Requires make, model, and year. |
engine | string | Optional | Engine description as returned by the engines list. Requires all previous parameters. |
variant | string | Optional | Variant name (select_text) as returned by the variants list. Requires all previous parameters. Returns full vehicle records. |
Full walkthrough: Toyota Hilux 2019
Seven requests take you from the makes list to a vehicle_id. Each command below is copy-paste runnable. We use curl -G --data-urlencode so you can write the raw values and let curl encode them; -G sends them as a query string.
Step 1 -- no parameters returns every make:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"data": [
{
"type": "make",
"data": ["ABARTH", "ACE EV", "ACURA", "ALEXANDER DENNIS", "ALFA ROMEO", "ALPINE", "..."]
}
],
"total": 227,
"duration_ms": 19.1
}Step 2 -- add make to list that make's models:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY" \ --data-urlencode "make=TOYOTA"
{
"success": true,
"data": [
{
"type": "model",
"data": ["4 RUNNER", "4 RUNNER DELUXE", "4 RUNNER RV6, V6 SR5", "4 RUNNER SR5", "86 GT", "86 GT, GTS", "..."]
}
],
"total": 328,
"duration_ms": 4.67
}Step 3 -- add model to list its years, newest first:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY" \ --data-urlencode "make=TOYOTA" \ --data-urlencode "model=HILUX"
{
"success": true,
"data": [
{
"type": "year",
"data": [2025, 2024, 2023, 2022, 2021, 2020, "...", 1969, 1968]
}
],
"total": 58,
"duration_ms": 1.05
}Step 4 -- add year to list series/chassis codes. Some vehicles have an empty string for series (see the rules above):
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY" \ --data-urlencode "make=TOYOTA" \ --data-urlencode "model=HILUX" \ --data-urlencode "year=2019"
{
"success": true,
"data": [
{
"type": "series",
"data": ["GUN126R -GUN126", "GUN136R -GUN136"]
}
],
"total": 2,
"duration_ms": 0.82
}Step 5 -- add series to list engines. For a vehicle with no series code you would send --data-urlencode "series=":
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY" \ --data-urlencode "make=TOYOTA" \ --data-urlencode "model=HILUX" \ --data-urlencode "year=2019" \ --data-urlencode "series=GUN126R -GUN126"
{
"success": true,
"data": [
{
"type": "engine",
"data": ["2.8L, DIE 1GDFTV I4 16v DOHC I/C Turbo CRD {130kW}"]
}
],
"total": 1,
"duration_ms": 0.95
}Note the two spaces after 2.8L,. They are part of the value and must be sent as-is.
Step 6 -- add engine to list variant names:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \
-H "X-API-Key: pk_live_YOUR_API_KEY" \
--data-urlencode "make=TOYOTA" \
--data-urlencode "model=HILUX" \
--data-urlencode "year=2019" \
--data-urlencode "series=GUN126R -GUN126" \
--data-urlencode "engine=2.8L, DIE 1GDFTV I4 16v DOHC I/C Turbo CRD {130kW}"{
"success": true,
"data": [
{
"type": "variant",
"data": ["4D Ute -, 4WD MR0HA3CD [THAILAND], AT/MT"]
}
],
"total": 1,
"duration_ms": 0.69
}Step 7 -- add variant to get the vehicle records. The two spaces before [THAILAND] are part of the value:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \
-H "X-API-Key: pk_live_YOUR_API_KEY" \
--data-urlencode "make=TOYOTA" \
--data-urlencode "model=HILUX" \
--data-urlencode "year=2019" \
--data-urlencode "series=GUN126R -GUN126" \
--data-urlencode "engine=2.8L, DIE 1GDFTV I4 16v DOHC I/C Turbo CRD {130kW}" \
--data-urlencode "variant=4D Ute -, 4WD MR0HA3CD [THAILAND], AT/MT"{
"success": true,
"data": [
{
"type": "vehicle",
"data": [
{
"vehicle_id": 1346446,
"select_text": "4D Ute -, 4WD MR0HA3CD [THAILAND], AT/MT",
"description": "15~20 TOYOTA HILUX 2.8L DIESEL",
"long_description": "TOYOTA HILUX 10/2015~04/2020"
}
]
}
],
"total": 1,
"duration_ms": 1.58
}If you prefer to build the URL yourself, this is the same step 7 request with every value percent-encoded (the result is identical):
curl -s "https://api.plateapi.com.au/api/v1/vehicles?make=TOYOTA&model=HILUX&year=2019\ &series=GUN126R%20-GUN126\ &engine=2.8L%2C%20%20DIE%201GDFTV%20I4%2016v%20DOHC%20I%2FC%20Turbo%20CRD%20%7B130kW%7D\ &variant=4D%20Ute%20-%2C%204WD%20MR0HA3CD%20%20%5BTHAILAND%5D%2C%20AT%2FMT" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
vehicle_id identifies the variant, not the year: the same variant returns the same ID for every model year it was sold. It is the same ID that /api/v1/lookup returns on a vehicle, and the key for /api/v1/vehicles/{vehicle_id}.
Motorcycle walkthrough: Yamaha MT-07 2021 NEW
Exactly the same as the car walkthrough above, but add type=motorcycle to every request. Leave it out on any step and you are back in the car cascade: make=YAMAHA without type returns an empty list (Yamaha makes no cars here), and make=HONDA returns Honda's cars, not its bikes.
Step 1 -- the motorcycle makes:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \ -H "X-API-Key: pk_live_YOUR_API_KEY" \ --data-urlencode "type=motorcycle"
{
"success": true,
"data": [
{
"type": "make",
"data": ["ACCESS", "ACCOSSATO", "ADIVA", "ADLER", "ADLY", "AEON", "..."]
}
],
"total": 428,
"duration_ms": 23.36
}Steps 2 to 6 work as for the Hilux, one parameter at a time. Motorcycles usually have a frame code as their series (or an empty one), and the series level matters: the 2021 MT-07 has six, the full-power HOA (55 kW) and the learner-approved LA (38 kW) versions, each with a new frame code from mid-2021. Step 7, the 2021-22 full-power bike:
curl -s -G "https://api.plateapi.com.au/api/v1/vehicles" \
-H "X-API-Key: pk_live_YOUR_API_KEY" \
--data-urlencode "type=motorcycle" \
--data-urlencode "make=YAMAHA" \
--data-urlencode "model=MT-07" \
--data-urlencode "year=2021" \
--data-urlencode "series=RM332" \
--data-urlencode "engine=689cc, M419E I2 8v DOHC CARB {55kW}" \
--data-urlencode "variant=HOA, 2 Wheel Road, RWD, MT"{
"success": true,
"data": [
{
"type": "vehicle",
"data": [
{
"vehicle_id": 7076553,
"select_text": "HOA, 2 Wheel Road, RWD, MT",
"description": "21~22 YAMAHA MT-07 0.7L",
"long_description": "YAMAHA MT-07 HOA . RM332 07/2021 ~ 12/2022 2 Wheels PETROL 0.7 litre, M419E I2 8v DOHC CARB {55KW}"
}
]
}
],
"total": 1,
"duration_ms": 1.64
}The same walk in code. Each call adds type for you, so the rest is the car walk unchanged:
import requests
API = "https://api.plateapi.com.au/api/v1/vehicles"
HEADERS = {"X-API-Key": "pk_live_YOUR_API_KEY"}
def level(**params):
"""One level of the motorcycle cascade."""
r = requests.get(API, params={"type": "motorcycle", **params}, headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()["data"][0]["data"]
path = {"make": "YAMAHA", "model": "MT-07", "year": 2021}
print(level(**path)) # ['RM044', 'RM04F', 'RM04G', 'RM161', 'RM332', 'RM338']
path["series"] = "RM332"
path["engine"] = level(**path)[0] # '689cc, M419E I2 8v DOHC CARB {55kW}'
path["variant"] = level(**path)[0] # 'HOA, 2 Wheel Road, RWD, MT'
for v in level(**path):
print(v["vehicle_id"], v["description"]) # 7076553 21~22 YAMAHA MT-07 0.7Lconst API = "https://api.plateapi.com.au/api/v1/vehicles";
async function level(params) {
const qs = new URLSearchParams({ type: "motorcycle", ...params });
const r = await fetch(API + "?" + qs, {
headers: { "X-API-Key": process.env.PLATEAPI_KEY },
});
const body = await r.json();
return body.data[0].data;
}
const path = { make: "YAMAHA", model: "MT-07", year: "2021", series: "RM332" };
path.engine = (await level(path))[0]; // "689cc, M419E I2 8v DOHC CARB {55kW}"
path.variant = (await level(path))[0]; // "HOA, 2 Wheel Road, RWD, MT"
const [bike] = await level(path);
console.log(bike.vehicle_id, bike.description); // 7076553 21~22 YAMAHA MT-07 0.7LThe ID works everywhere a car's does. /api/v1/vehicles/{vehicle_id} needs no type and tells you which kind it is, and a motorcycle plate lookup returns the same ID as the motorcycle cascade:
curl -s "https://api.plateapi.com.au/api/v1/vehicles/7076553" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"vehicle": {
"vehicle_id": 7076553,
"vehicle_type": "motorcycle",
"make": "YAMAHA",
"model": "MT-07",
"year_range": "2021-2022",
"years": [2021, 2022],
"series": "RM332",
"engine": "689cc, M419E I2 8v DOHC CARB {55kW}",
"variant": "HOA, 2 Wheel Road, RWD, MT",
"drive": "RWD",
"transmission": "MT",
"detail": "full",
...
},
"duration_ms": 2.09
}Building dropdowns
The browser example below drives six <select> elements and shows the final vehicle records. It calls your own backend at /api/vehicles, not PlateAPI directly, because anything in browser JavaScript is visible to your visitors and would expose your API key. The backend adds the key and forwards the query:
app.get("/api/vehicles", async (req, res) => {
// Forwards every parameter, type included, so one backend serves car and
// motorcycle dropdowns. Optional: only forward the seven known parameters.
const qs = new URLSearchParams(req.query);
const r = await fetch("https://api.plateapi.com.au/api/v1/vehicles?" + qs, {
headers: { "X-API-Key": process.env.PLATEAPI_KEY },
});
res.status(r.status).json(await r.json());
});The page markup is six selects with ids make, model, year, series, engine, variant and an empty <ul id="result">. Each change re-fetches the next level; the empty series is shown as "(no series code)" and sent back as series=.
const ORDER = ["make", "model", "year", "series", "engine", "variant"];
const selects = ORDER.map(id => document.getElementById(id));
const result = document.getElementById("result");
async function level(params) {
const res = await fetch("/api/vehicles?" + new URLSearchParams(params));
if (!res.ok) throw new Error("HTTP " + res.status);
const json = await res.json();
if (!json.success) throw new Error(json.error || "query failed");
return json.data[0]; // { type, data }
}
function fill(select, values) {
select.replaceChildren(new Option("Select...", ""));
for (const v of values) {
select.add(new Option(v === "" ? "(no series code)" : v, v));
}
select.disabled = false;
}
selects.forEach((select, i) => {
select.addEventListener("change", async () => {
selects.slice(i + 1).forEach(s => { s.replaceChildren(); s.disabled = true; });
result.replaceChildren();
if (select.selectedIndex === 0) return; // placeholder chosen
const params = {}; // everything chosen so far
for (let j = 0; j <= i; j++) params[ORDER[j]] = selects[j].value;
const { type, data } = await level(params);
if (type === "vehicle") { // after the variant: final records
for (const v of data) {
const li = document.createElement("li");
li.textContent = v.vehicle_id + " -- " + v.select_text;
result.append(li);
}
} else {
fill(selects[i + 1], data);
}
});
});
selects.forEach(s => { s.disabled = true; });
level({}).then(r => fill(selects[0], r.data)); // load the makesThe same walk in Python. This one reads the key from an environment variable and takes the first option at each level; in a real integration you would let the user choose or match on select_text:
import os
import requests
API = "https://api.plateapi.com.au/api/v1/vehicles"
HEADERS = {"X-API-Key": os.environ["PLATEAPI_KEY"]}
def level(**params):
r = requests.get(API, params=params, headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()["data"][0]["data"] # requests URL-encodes the values
params = {"make": "TOYOTA", "model": "HILUX", "year": 2019}
for key in ("series", "engine", "variant"):
params[key] = level(**params)[0] # first option; "" is a valid value
print(key, "->", repr(params[key]))
for v in level(**params): # final level: vehicle records
print(v["vehicle_id"], v["select_text"])From a plate to a database record NEW
A plate lookup returns a vehicle_id on vehicle and on each entry in alternatives. Pass it to GET /api/v1/vehicles/{vehicle_id} to get the full record, with no quota cost:
# 1. Look up the plate (counts toward quota; TEST123 is free)
curl -s "https://api.plateapi.com.au/api/v1/lookup?plate=TEST123&state=NSW" \
-H "X-API-Key: pk_live_YOUR_API_KEY"
# ... "vehicle": { "vehicle_id": 1380801, ... }
# 2. Fetch the full record for that ID
curl -s "https://api.plateapi.com.au/api/v1/vehicles/1380801" \
-H "X-API-Key: pk_live_YOUR_API_KEY"- When the lookup's
matchis"multiple", the plate could be several variants. Either store every candidatevehicle_id(the vehicle and allalternatives), for example to show parts that fit any of them, or ask the customer to pick one. vehicle_idisnullwhen the variant could not be identified with certainty. An ID is never guessed.- The ID is stable and identifies the variant, not the year, so it is safe to store against a customer's garage or an order. The record's
yearslist tells you which model years the variant covers.
Linking your products (SKUs) to vehicles NEW
The point of a stable vehicle_id is that you tag each product once with the vehicles it fits, and from then on a plate lookup tells you which products to show. Three steps: a fitment table, a one-off mapping of your existing catalogue, and a query on every lookup.
1. A fitment table. Many-to-many between your products and vehicle_ids. One ID covers every model year of that variant, so a part that fits a 2015-2020 Hilux SR needs one row per variant, not one per year.
CREATE TABLE fitments ( sku text NOT NULL, vehicle_id integer NOT NULL, -- PlateAPI vehicle_id PRIMARY KEY (sku, vehicle_id) );
2. Map your existing catalogue once. Most catalogues describe fitment as text ("Toyota Hilux 2015-2020 2.8L diesel"). Resolve each description to IDs by walking the cascade: every model name that starts with the base model (the database files trims as separate models: HILUX, HILUX SR, HILUX SR5 EXTRA CAB ...), each year in the range, every series, the engines that match, and the vehicles under them. This script does exactly that:
import os, time, requests
API = "https://api.plateapi.com.au/api/v1/vehicles"
HEADERS = {"X-API-Key": os.environ["PLATEAPI_KEY"]}
def level(**params):
r = requests.get(API, params=params, headers=HEADERS, timeout=15)
r.raise_for_status()
body = r.json()
if not body["success"]:
raise RuntimeError(body.get("error", "query failed"))
time.sleep(0.15) # stays under 500 requests/minute
return body["data"][0]["data"]
def vehicle_ids(make, base_model, year_from, year_to, engine_contains=""):
"""Every vehicle_id for a make, all model names starting with base_model,
a year range, and engines containing a substring. One ID per variant."""
ids = {}
models = [m for m in level(make=make)
if m == base_model or m.startswith(base_model + " ")]
for model in models:
years = [y for y in level(make=make, model=model) if year_from <= y <= year_to]
for year in years:
for series in level(make=make, model=model, year=year):
for engine in level(make=make, model=model, year=year, series=series):
if engine_contains.upper() not in engine.upper():
continue
for variant in level(make=make, model=model, year=year,
series=series, engine=engine):
for v in level(make=make, model=model, year=year, series=series,
engine=engine, variant=variant):
ids[v["vehicle_id"]] = model + " / " + v["select_text"]
return ids
ids = vehicle_ids("TOYOTA", "HILUX", 2015, 2020, engine_contains="2.8L")
for vehicle_id, text in sorted(ids.items()):
print(vehicle_id, text)
# then insert one fitments row per (sku, vehicle_id)1322020 HILUX SR5 EXTRA CAB / SR5 EXTRA CAB, 4D Ute -, 4WD MR0HA3CC [THAILAND], AT/MT 1330126 HILUX SR5 / SR5, 4D Ute -, 4WD MR0HA3CD [THAILAND], AT/MT 1346446 HILUX / 4D Ute -, 4WD MR0HA3CD [THAILAND], AT/MT 1380801 HILUX SR / SR, 4D Ute -, 4WD MR0KA3CD [THAILAND], AT/MT 1428997 HILUX / 4D Ute -, RWD MR0AA3DD [THAILAND], AT 1515123 HILUX SR5 HI-RIDER / SR5 HI-RIDER, 4D Ute -, RWD MR0GA3DD [THAILAND], AT/MT ...
- Cache the levels you have already fetched (makes, each make's models, each model's years). The same nodes recur across products, so a cached walk makes far fewer requests.
- Map a large catalogue in batches. Mapping your own product catalogue is a permitted use of the vehicle database. Run it in batches over time rather than as one continuous job, and contact support before mapping a very large catalogue so we can help.
- Narrow by engine, body or drive when a part is specific. The engine string carries displacement and code;
select_textcarries doors, body, drive and gearbox ("4D Ute", "4WD", "AT/MT"). - Map every ID the walk returns. The text alone does not tell variants apart: several of the 21 above share identical text but are different vehicles (the 2015-2020 130kW and 2020-2025 150kW versions of one trim), and a lookup returns whichever one the plate is.
- Store the IDs, not the text. IDs are stable; names and series strings can change.
- Import format: a two-column CSV,
sku,vehicle_id, is all you need.
3. On every lookup, query the table.
-- lookup response: vehicle.vehicle_id = 1380801, match = "exact" SELECT sku FROM fitments WHERE vehicle_id = 1380801; -- match = "multiple": the plate could be several variants. -- Either show everything that fits any candidate ... SELECT DISTINCT sku FROM fitments WHERE vehicle_id IN (1380801, 1346446, 1330126); -- ... or show the candidates' descriptions, let the customer confirm -- their variant, and query the one ID they chose.
Keeping it current. New variants get new IDs as they are added; a vehicle first seen on a plate starts as a basic record and keeps its ID when the full record arrives. Re-run the mapping for products whose fitment text has not been resolved yet (monthly is plenty) and never re-map rows you already have. If a lookup returns a vehicle_id with no fitments row, that is a gap in your mapping, not an unknown vehicle: fetch the record by ID and map the SKU.
Fetch one vehicle by ID NEW
Returns the record for one vehicle_id. This is how a plate lookup links to the vehicle database: take the vehicle_id from a lookup response and fetch its record here. Same auth and rate limit as the cascade, and no quota consumption.
curl -s "https://api.plateapi.com.au/api/v1/vehicles/1380801" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"vehicle": {
"vehicle_id": 1380801,
"vehicle_type": "car",
"make": "TOYOTA",
"model": "HILUX SR",
"year_range": "2015-2020",
"lowest_year": 2015,
"highest_year": 2020,
"years": [2015, 2016, 2017, 2018, 2019, 2020],
"description": "15~20 TOYOTA HILUX 2.8L DIESEL",
"long_description": "TOYOTA HILUX 10/2015~04/2020",
"series": "GUN126R -GUN126",
"engine": "2.8L, DIE 1GDFTV I4 16v DOHC I/C Turbo CRD {130kW}",
"variant": "SR, 4D Ute -, 4WD MR0KA3CD [THAILAND], AT/MT",
"body": "UTE",
"body_size": null,
"doors": 4,
"drive": "4WD",
"transmission": "AT/MT",
"detail": "full"
},
"duration_ms": 1.2
}body, body_size (SUVs: COMPACT, MEDIUM, FULL-SIZE), doors, drive (4WD, AWD, RWD, FWD, 6X4 ...) and transmission (AT, MT, AT/MT, CVT) are read out of the variant name and are null when it does not carry them. If the ID you request has been merged into another (the database occasionally held one vehicle under two IDs), the response is the current record: compare its vehicle_id with the one you sent and update what you stored. vehicle_type is car or motorcycle (which cascade the vehicle is in).
detail is "full" for a complete record. It is "basic" for a vehicle that has an ID but whose full record has not been added yet: make, model, year range, description and engine displacement are filled, while series, variant and long_description are null and years is empty. The ID does not change when the full record is added. A basic record (a 1993 Toyota Camry):
curl -s "https://api.plateapi.com.au/api/v1/vehicles/7295503" \ -H "X-API-Key: pk_live_YOUR_API_KEY"
{
"success": true,
"vehicle": {
"vehicle_id": 7295503,
"vehicle_type": "car",
"make": "TOYOTA",
"model": "CAMRY",
"year_range": "1993-1997",
"lowest_year": 1993,
"highest_year": 1997,
"years": [],
"description": "93~97 TOYOTA CAMRY 2.2L PETROL",
"long_description": null,
"series": null,
"engine": "2.2L",
"variant": null,
"body": null,
"body_size": null,
"doors": null,
"drive": null,
"transmission": null,
"detail": "basic"
},
"duration_ms": 1.58
}Errors
{
"success": false,
"vehicle": null,
"code": "not_found",
"error": "No vehicle with that vehicle_id.",
"duration_ms": 0.49
}{
"detail": "Invalid path parameters. vehicle_id: Input should be a valid integer, unable to parse string as an integer"
}{
"success": false,
"vehicle": null,
"code": "unavailable",
"error": "Vehicle database temporarily unavailable. Please retry.",
"duration_ms": 0.3
}A 503 means the vehicle database is briefly unavailable: wait for the Retry-After interval and retry. The cascade answers an outage the same way, with "data": [], "total": 0 in place of "vehicle": null. A missing API key returns 401 with {"detail": "API key required. Pass X-API-Key header."}. On the cascade, by contrast, an unknown make, model or other value is not an error: it returns 200 with an empty list.
Tips
- Cache option lists. Makes, models and years change rarely. Cache the makes list and each make's models for a day (in your backend or CDN) and you will make far fewer requests.
- Mind concurrency. The limit is 500 requests per minute per account, shared by the cascade and the by-ID endpoint. A dropdown UI makes one request per click, so this is rarely an issue. Jobs that resolve many IDs should pace their requests, reuse what they have already fetched, and back off on
429(wait for theRetry-Afterheader). - Vehicles with no series code. The series list then contains an empty string. Show it as "(no series code)" or "Standard", keep the real value
""in the option, and sendseries=to continue. - Empty list means a mismatched value. Re-send exactly what the previous level returned, including double spaces, and let your HTTP library encode it.
- Free and Lite plans get 403. Vehicle database endpoints need a Starter plan or above. Check the plan before showing a vehicle picker, or fall back to plate lookup only.