How to Look Up Australian Number Plates in Python
This guide covers looking up Australian registration plates from Python with plateapi, PlateAPI's official SDK. It handles the HTTP calls, retries and error codes for you, so a lookup is one line. Every example below was run against the live API on 4 October 2026. The lookup examples use the free sandbox plate TEST123, so you can run them without spending a lookup; the vehicle database examples need a Starter plan or above.
Install
pip install plateapi
It needs Python 3.8 or later and depends only on requests. The SDK's code is on GitHub and the package on PyPI. You also need an API key: create a free account (20 lookups a month, no card) and copy the key from your dashboard. Keep it in an environment variable rather than in your code.
Your first lookup
import os
from plateapi import PlateAPI
client = PlateAPI(os.environ["PLATEAPI_KEY"])
result = client.lookup("TEST123", "NSW") # the free sandbox plate
if result.success:
v = result.vehicle
print(v.year_range, v.make, v.model, v.body, v.engine)
print(v.vehicle_id, result.match, result.request_id)
else:
print(result.code, result.error)2015-2020 TOYOTA HILUX UTE 2.8L 1380801 exact req_33d68941f9dfb9dcec7d8cab
The state is one of ACT, NSW, NT, QLD, SA, TAS, VIC or WA. The SDK upper-cases and trims the plate and state, and raises ValueError for an unknown state before sending anything. Swap TEST123 for a real plate when you are ready; each real plate counts as one lookup.
A plate that does not resolve is a normal result, not an exception: result.success is False and result.code is "not_found". It still counts toward your quota, so check the plate format before you send it.
vehicle_id and match
Every vehicle in a result carries vehicle_id, PlateAPI's stable ID for that exact variant. Store it instead of the make and model text: it is the same ID the vehicle database uses, it never changes, and it is the key to link your own records, such as products or service history, to vehicles. result.match says how to read the answer:
"exact": one variant, inresult.vehicle."multiple": several trims of one make and model on sale at the same time.result.vehicleis the first, the rest are inresult.alternatives."ambiguous": different models or generations.result.vehicle.vehicle_idisNone(an ID is never guessed) andresult.alternativeslists every candidate with its ID.
if result.match == "exact":
save(result.vehicle.vehicle_id)
else:
for alt in result.alternatives:
print(alt.vehicle_id, alt.year_range, alt.description)
save_all(result.candidate_ids) # every non-null ID, primary firstFor a customer-facing form, showing the candidates and letting the customer pick theirs is usually best. The SKU linking guide shows how stores use these IDs to list the parts that fit.
The full vehicle record
On Starter plans and above, vehicle_by_id() returns the full record for an ID, including the series, full engine description, drive and gearbox. It does not use lookup quota.
record = client.vehicle_by_id(1380801) print(record.make, record.model, record.year_range) print(record.series, "|", record.engine) print(record.body, record.doors, record.drive, record.transmission, record.years)
TOYOTA HILUX SR 2015-2020
GUN126R -GUN126 | 2.8L, DIE 1GDFTV I4 16v DOHC I/C Turbo CRD {130kW}
UTE 4 4WD AT/MT [2015, 2016, 2017, 2018, 2019, 2020]Note the model: the lookup said HILUX, the record says HILUX SR. The wording can differ, the ID does not, which is why you integrate on the ID.
Make, model and year menus, cars and motorcycles
On Starter plans and above, vehicles() walks the vehicle database one level at a time (no lookup quota used): no arguments returns the makes, make returns its models, model its years, and so on down to vehicle records. Pass vehicle_type="motorcycle" for the separate motorcycle cascade.
makes = client.vehicles() print(makes.type, makes.total, makes.data[:3]) years = client.vehicles(make="TOYOTA", model="HILUX") print(years.type, years.data[:5]) bikes = client.vehicles(make="YAMAHA", vehicle_type="motorcycle") print(bikes.type, bikes.total, bikes.data[:5])
make 227 ['ABARTH', 'ACE EV', 'ACURA'] year [2025, 2024, 2023, 2022, 2021] model 628 ['1700', 'AEROX', 'AG100', 'AG125', 'AG175']
Pass each value back exactly as it was returned; the SDK URL-encodes it. To collect every ID for a model range in one go, walk_vehicles() does the whole walk and yields each vehicle once. The motorcycle guide has a full bike example.
Errors and retries
Errors are exceptions with the API's own message, HTTP status and code. The SDK already retries connection errors, rate limits and temporary server errors with backoff, so what reaches your code is worth handling:
from plateapi import (
PlateAPIError, QuotaExceededError, RateLimitError, ServerError,
)
try:
result = client.lookup(plate, state)
except QuotaExceededError:
... # monthly lookup quota used up
except RateLimitError as e:
... # per-minute limit: retry after e.retry_after seconds
except ServerError as e:
... # temporary failure, not billed: retry later (e.code, e.request_id)
except PlateAPIError as e:
... # anything else: invalid key, IP not allowed, network failureA lookup that cannot be completed returns 503 with code lookup_failed and is never billed. Quote request_id if you contact support about a specific request.
Testing with TEST123
The sandbox plate returns a fixed vehicle instantly with any state. It is free, it does not touch your quota or rate limit, and it is a real record (vehicle_id 1380801), so it works in CI:
import os
import pytest
from plateapi import PlateAPI
@pytest.fixture(scope="session")
def client():
with PlateAPI(os.environ["PLATEAPI_KEY"]) as c:
yield c
def test_sandbox_plate_is_a_hilux(client):
result = client.lookup("TEST123", "VIC")
assert result.success and result.sandbox
assert result.match == "exact"
assert result.vehicle.make == "TOYOTA"
assert result.vehicle.vehicle_id == 1380801$ pytest -q test_vehicle_lookup.py . [100%] 1 passed in 0.18s
Next steps
- The SDK README lists every field, the usage and logs calls, and the retry settings.
- The lookup reference and vehicle database reference document the API underneath.
- Prefer plain HTTP? The developer guide has cURL,
requestsand JavaScript examples.
PlateAPI turns Australian number plates into structured vehicle data -- one REST endpoint, all eight states, free tier included.