Plate Lookup
Look up a vehicle by registration plate and state. Returns structured vehicle data including the model year range, make, model, body type, and engine displacement for cars, motorcycles, and commercial vehicles. Pass detailed=true to include an extended vehicle description when available. The first lookup of a plate is performed live at request time. A successful result is then cached for up to 3 days, so repeat lookups of the same plate and state in that window return instantly; a plate change or a newly registered vehicle can take up to 3 days to show. Not-found results are not cached.
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Required | Your API key (e.g. pk_live_...). Available from your dashboard. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
plate | string | Required | Registration plate number (e.g. ABC123). Case-insensitive. Letters, numbers, dashes, and spaces only, maximum 10 characters -- dashes and spaces are stripped before lookup. |
state | string | Required | Australian state or territory code: ACT, NSW, NT, QLD, SA, TAS, VIC, WA |
detailed | boolean | Optional | Set to true to include detailed_description in the vehicle object. This field contains extended vehicle data (chassis codes, drivetrain, fuel type, engine code, and power output) when available. Defaults to false. May return null for some vehicles. |
Example response
{
"success": true,
"vehicle": {
"year_range": "2003-2006",
"lowest_year": 2003,
"highest_year": 2006,
"make": "TOYOTA",
"model": "COROLLA",
"description": "03~06 TOYOTA COROLLA SPORTIVO 1.8L PETROL",
"body": "HATCHBACK",
"engine": "1.8L",
"vehicle_id": 5152708
},
"source": "plateapi",
"duration_ms": 1509.96,
"request_id": "req_7f3a9c1b4e8d2f6a0c5e3d1b",
"alternatives": [
{
"year_range": "2003-2006",
"lowest_year": 2003,
"highest_year": 2006,
"make": "TOYOTA",
"model": "COROLLA",
"description": "03~06 TOYOTA COROLLA ASCENT 1.8L PETROL",
"body": "SEDAN",
"engine": "1.8L",
"vehicle_id": 1857621
}
],
"match": "multiple"
}Response fields
| Field | Type | Description |
|---|---|---|
success | boolean | Whether a vehicle was found |
vehicle.year_range | string | Model year range (e.g. 2003-2006). Vehicles still in production return an open range like 2020-ON. |
vehicle.lowest_year | integer | First model year of the range |
vehicle.highest_year | integer|null | Last model year of the range. null when the model is still in production. |
vehicle.make | string | Manufacturer (e.g. TOYOTA, HOLDEN, FORD) |
vehicle.model | string | Model name |
vehicle.description | string | Full vehicle description string |
vehicle.body | string|null | Body type (SEDAN, HATCHBACK, UTE, etc.) |
vehicle.engine | string|null | Engine displacement (e.g. 1.8L) |
vehicle.vehicle_id NEW | integer|null | PlateAPI ID of this exact vehicle variant. Stable: the same variant always has the same ID, on every lookup and in the vehicle database (/api/v1/vehicles). null when the variant could not be identified with certainty -- an ID is never guessed. With match: "multiple" it identifies the first candidate, not necessarily the plate's own trim: check the alternatives too. |
vehicle.detailed_description | string|null | Extended vehicle description including chassis codes, drivetrain, fuel type, engine code, and power output. Only present when detailed=true is passed. May be null even when requested -- not all vehicles have this data. |
source | string | Always "plateapi". Present on successful lookups. |
duration_ms | number | Total lookup time in milliseconds |
alternatives | array | Other possible vehicle matches (e.g. different engine variants). Each has the same fields as vehicle, including its own vehicle_id. |
match NEW | string | "exact": one variant. "multiple": several trims of one make and model, all on sale at the same time; vehicle is the first with its vehicle_id, the rest are in alternatives. "ambiguous": the candidates are different makes or models, or were not all on sale at the same time (for example different generations), so vehicle carries no vehicle_id (it would be a guess) and alternatives lists every candidate, including the one shown as vehicle, each with its ID. Present on successful lookups. |
request_id | string | Unique identifier for this request (e.g. req_7f3a9c1b4e...), also sent as the X-Request-ID response header. Include when contacting support. |
Handling multiple matches
Some plates map to more than one variant -- typically trims of the same model. The first candidate (in a fixed order, so the same plate always gives the same one) is returned as vehicle and the rest arrive in alternatives, each with the same fields and its own vehicle_id. When every candidate is a trim of one make and model and all of them were on sale at the same time, match is "multiple":
{
"success": true,
"vehicle": {
"year_range": "2020-ON",
"lowest_year": 2020,
"highest_year": null,
"make": "ISUZU",
"model": "D-MAX",
"description": "20~ ISUZU D-MAX 3.0L DIESEL",
"body": null,
"engine": "3.0L DIE 4JJ3-TCX I4 16v DOHC I/C Turbo CRD {140kW}",
"vehicle_id": 1981765
},
"source": "plateapi",
"duration_ms": 1219.6,
"request_id": "req_4f1a9c2e7b3d5a8f0c6e1b2d",
"alternatives": [
{
"year_range": "2020-ON",
"lowest_year": 2020,
"highest_year": null,
"make": "ISUZU",
"model": "D-MAX LS-U, SX SPACECAB",
"description": "20~ ISUZU D-MAX LS-U, SX SPACECAB 3.0L DIESEL",
"body": null,
"engine": "3.0L DIE 4JJ3-TCX I4 16v DOHC I/C Turbo CRD {140kW}",
"vehicle_id": 1989953
},
{
"year_range": "2020-ON",
"lowest_year": 2020,
"highest_year": null,
"make": "ISUZU",
"model": "D-MAX SX",
"description": "20~ ISUZU D-MAX SX 3.0L DIESEL",
"body": null,
"engine": "3.0L DIE 4JJ3-TCX I4 16v DOHC I/C Turbo CRD {140kW}",
"vehicle_id": 2393225
}
],
"match": "multiple"
}When the candidates are different makes or models, or were not all on sale at the same time (a generation change, or trims that only overlapped for one model year), match is "ambiguous": vehicle has vehicle_id null and alternatives lists every candidate, including the one shown as vehicle, each with its ID.
For parts fitment, do not silently pick the first match -- showing a quick "which one is yours?" list avoids wrong-part orders:
const data = await res.json();
if (data.success && data.match === "multiple") {
// Trims of one model: ask the customer to confirm theirs
const options = [data.vehicle, ...data.alternatives];
showVariantPicker(options); // e.g. radio list of descriptions
} else if (data.success && data.match === "ambiguous") {
// Different models or generations: vehicle has no vehicle_id,
// alternatives already contains every candidate with its ID
showVariantPicker(data.alternatives);
} else if (data.success) {
confirmVehicle(data.vehicle);
}Integrate on vehicle_id, not on text. A lookup's make, model and description can be worded differently from the vehicle record the ID points to: the sandbox plate TEST123 returns model HILUX, while record 1380801 has model HILUX SR. The ID is the same either way.
Every option carries its own vehicle_id, so once the customer picks one you can store that ID, or fetch the full record from /api/v1/vehicles/{vehicle_id}. If you need every vehicle the plate could be (for example to show parts that fit any of them), use all the IDs.
Error Responses
When no vehicle is found, the API returns 200 OK with a JSON object containing success: false, a machine-readable code, and an error field describing what went wrong. Invalid requests return 400, missing or invalid API keys return 401, keys that are not allowed to make the request return 403, and rate or quota limits return 429. When a lookup cannot be completed right now, the API returns 503: retry the request.
Note: lookups that complete but find no vehicle still count toward your monthly quota -- each lookup performs a full search regardless of the outcome. Requests rejected with 400, 401, 403, or 429 do not consume quota, and neither do lookups that end in 503 or internal_error.
| Status | Body | When | Uses quota |
|---|---|---|---|
200 | code: "not_found" | No vehicle was found for the plate and state. | Yes |
200 | code: "internal_error" | The lookup failed on our side. Try again. | No |
400 | detail | Invalid plate or state, or a missing parameter. | No |
401 | detail | No API key, an invalid or deactivated key, or an account whose email is not verified yet. | No |
403 | detail | The request comes from an IP address that is not on your key's IP allowlist (set in your dashboard), or the account is banned. | No |
429 | detail + Retry-After | Per-minute rate limit or monthly quota reached. | No |
503 | code: "lookup_failed" | The lookup could not be completed right now. Retry after a few seconds; repeated failures for one plate are worth reporting with the request_id. | No |
503 | detail + Retry-After | The service is briefly unavailable (rare). Retry after the Retry-After seconds. | No |
The vehicle database endpoints have their own error responses (Free and Lite keys get 403, an unknown vehicle_id gets 404, and an outage gets 503 with Retry-After): see Vehicle database.
{
"success": false,
"vehicle": null,
"code": "not_found",
"error": "Vehicle not found. Check your plate and try again.",
"duration_ms": 4521.33,
"request_id": "req_2c1d9e7a4b3f8e6d0a1b5c7e"
}Error examples
{
"detail": "Invalid plate. Plates may only contain letters, numbers, dashes, and spaces."
}{
"detail": "Invalid API key"
}{
"detail": "This API key is not allowed from your IP address."
}{
"detail": "This account has been permanently banned for violating our Terms of Service."
}Retry-After: 42
{
"detail": "Per-minute rate limit exceeded (10/min on starter plan). Slow down."
}Retry-After: 3600
{
"detail": "Monthly quota exceeded. Used 600/600 lookups on starter plan. Upgrade at plateapi.com.au"
}{
"success": false,
"vehicle": null,
"code": "lookup_failed",
"error": "Lookup temporarily unavailable. Please retry.",
"duration_ms": 6012.4,
"request_id": "req_9b2e4c1d7a3f5e8b0c6d2a4f"
}Retry-After: 5
{
"detail": "Service temporarily unavailable. Please retry."
}Rate Limits
Requests are rate-limited per minute based on your plan, over a sliding 60-second window: each request counts against the limit for 60 seconds after it was made, not until the top of the next minute. Exceeding the limit returns a 429 Too Many Requests response. Its Retry-After header is the number of seconds until your oldest request in the window expires (1 to 60), so waiting that long always frees a slot. A 429 for an exhausted monthly quota sends Retry-After: 3600 instead.
| Plan | Rate Limit | Monthly Lookups |
|---|---|---|
| Free | 1 request/min | 20 |
| Lite | 5 requests/min | 200 |
| Starter | 10 requests/min | 600 |
| Growth | 30 requests/min | 2,500 |
| Enterprise | 60 requests/min | 18,000 |
| Enterprise+ | Custom | Custom (contact us) |
Tracking your usage from response headers
Every successful lookup includes rate limit headers, so your application can track quota without extra API calls:
| Header | Description |
|---|---|
X-RateLimit-Limit | Your monthly lookup allowance |
X-RateLimit-Remaining | Lookups left in the current billing period |
X-RateLimit-Plan | Your current plan slug (e.g. starter) |
resp = requests.get(
"https://api.plateapi.com.au/api/v1/lookup",
params={"plate": "ABC123", "state": "VIC"},
headers={"X-API-Key": "pk_live_YOUR_API_KEY"},
)
remaining = resp.headers.get("X-RateLimit-Remaining")
if int(remaining) < 50:
notify_team(f"PlateAPI quota low: {remaining} lookups left")