Vehicles & search
List years, makes and models; search and resolve free text; check a whole list; stable vehicle ids.
Images are bounded to the open-source @meterapp/vehicle-db catalog. These free endpoints let you discover valid years, makes and models, search them, and turn a human phrase into exact image parameters — so you never guess and never see a 404. Every vehicle they return also carries a stable id you can store and send back instead of three names, and a VIN decode lands on the same ids. A catalog of your own is checked in one call and mapped to ids once.
Vehicle ids
Every catalog vehicle (one make, model and year) has an id such as veh_395yw8tn73ff8, the 2023 Ford F-150. It is veh_ plus 13 characters, deterministic and permanent: the same vehicle has the same id in every response, on every deploy and after every catalog release, so it is safe to store as a foreign key in your own tables. Ids appear on every catalog response below, on VIN decodes, in the vehicle_id of every echoed image object and on 3D models.
Send an id as vehicle= to GET /api/v1/images/car, as "vehicle" in a signed URL item or a 3D request, and to the MCP tools — in place of make, model and year. Sending both is a 400; an id that names no vehicle is a 404.
/api/v1/vehicles/{id}curl "https://carimage.dev/api/v1/vehicles/veh_395yw8tn73ff8"{
"data": {
"id": "veh_395yw8tn73ff8",
"make": {
"id": 460,
"name": "Ford",
"slug": "ford"
},
"model": {
"name": "F-150",
"slug": "f-150",
"vehicle_type": "Truck"
},
"year": 2023,
"years": [
2027,
2026,
2025,
2024,
2023,
"…",
1990
],
"year_ranges": [
{
"year_from": 1990,
"year_to": 2027
}
],
"image_path": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8",
"images": {
"front": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=front",
"front-3-4": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=front-3-4",
"side": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=side",
"rear-3-4": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=rear-3-4",
"rear": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=rear",
"rear-3-4-right": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=rear-3-4-right",
"side-right": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=side-right",
"front-3-4-right": "/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=front-3-4-right"
}
},
"request_id": "req_01j9x…"
}The lookup is public and cacheable for a day. A malformed id is a 400; a well-formed id that names no vehicle is a 404. image_path and images are ready to append &color=, &w= and the other image parameters to:
https://carimage.dev/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=side&color=1a2b3c&w=600&h=400&trim=1List years, makes and models
/api/v1/vehicles| Parameter | Type | Description |
|---|---|---|
year | integer | Restrict makes (and models) to those sold in this model year. |
makeId | integer | With year: list models for this make. IDs come from the makes response. |
q | string | Free-text search across makes and models (see below). Combine with year to filter. |
The response shape follows the parameters you pass:
# Years available in the catalog
curl -H "Authorization: Bearer $CAR_IMAGE_API_KEY" "https://carimage.dev/api/v1/vehicles"
# → { "data": { "years": [2027, 2026, …, 1990] }, "request_id": "…" }
# Makes sold in 2024
curl -H "Authorization: Bearer $CAR_IMAGE_API_KEY" "https://carimage.dev/api/v1/vehicles?year=2024"
# → { "data": { "makes": [{ "makeId": 449, "makeName": "Porsche", "slug": "porsche" }, …] } }
# Models for a make in 2024, each with the id of that model year
curl -H "Authorization: Bearer $CAR_IMAGE_API_KEY" "https://carimage.dev/api/v1/vehicles?year=2024&makeId=449"
# → { "data": { "models": [{ "name": "911", "slug": "911", "vehicle_type": "Passenger Car", "id": "veh_78qtwrgh37bkr" }, …] } }
# One model's years, each with its id
curl "https://carimage.dev/api/v1/vehicles?make=ford&model=f-150"
# → { "data": { "make": {…}, "model": {…}, "years": [2027, …, 1990], "year_ranges": [{ "year_from": 1990, "year_to": 2027 }],
# "vehicles": [{ "year": 2023, "id": "veh_395yw8tn73ff8" }, …] } }slug values as make and model, or the id as vehicle. Display names are normalized the same way on the server, so either works — slugs are just unambiguous, and an id is one value instead of three. Model lists under ?make=&year= and ?year=&makeId= carry id; a model without a year carries none, since an id names one year.Search
?q= searches makes and models with deterministic ranking: exact, prefix, token, substring, then conservative typo matching. Duplicate catalog rows are merged, and within a match kind the models with the longest production history rank first. A year inside the query is detected automatically. Model hits carry ids, one per year the model was sold, and id when the query (or year=) names a single year. Every word of the query must be in one vehicle's name, so fewer words find more; when no vehicle carries them all but the query names a make and then a model the image endpoints would read (mazda mx-5 miata with year=2018), the one result is that vehicle.
curl -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
"https://carimage.dev/api/v1/vehicles?q=mustang&year=2024"{
"data": {
"results": [
{
"kind": "model",
"make_id": 460,
"make_name": "Ford",
"make_slug": "ford",
"model_name": "Mustang",
"model_slug": "mustang",
"vehicle_type": "Passenger Car",
"id": "veh_f9eeprhqj2txc",
"ids": {
"2024": "veh_f9eeprhqj2txc"
},
"years": [
2024
],
"year_ranges": [
{
"year_from": 2024,
"year_to": 2024
}
],
"match_kind": "exact"
},
{
"kind": "model",
"make_id": 460,
"make_name": "Ford",
"make_slug": "ford",
"model_name": "Mustang Mach-E",
"model_slug": "mustang-mach-e",
"vehicle_type": "Passenger Car",
"id": "veh_27aft8cm3hgrf",
"ids": {
"2024": "veh_27aft8cm3hgrf"
},
"years": [
2024
],
"year_ranges": [
{
"year_from": 2024,
"year_to": 2024
}
],
"match_kind": "prefix"
}
]
},
"request_id": "req_01j9x…"
}Look a vehicle up from free text
/api/v1/images/resolveThe call to make before a render. Give it the year, make and model the way a person (or an LLM) would say them and get back exact request parameters, including the vehicle_id to render by. The catalog files cars under its own names, so the name you know is often not the one it uses: a 2018 Mazda MX-5 Miata is mx-5. A make followed by a model is read by the rules the image endpoints read them with, in the year you asked for, so the answer is the vehicle a render would serve.
Colors like "navy" or "charcoal" map onto the presets (a hex in the phrase is kept as is), and angles like "hero shot", "three-quarter view" or "from behind" map onto views. A description is not a name: "the electric one" or "a mid-2000s Civic" does not resolve. The resolver is rule-based and deterministic: the same phrase always resolves the same way.
| Parameter | Type | Description |
|---|---|---|
queryrequired | string | The phrase to resolve, e.g. "red 2018 mazda mx-5 miata side profile". |
year | integer | The model year; overrides any year in the phrase. |
make | string | A make you already know, to narrow the lookup. |
model | string | A model you already know. |
curl -X POST https://carimage.dev/api/v1/images/resolve \
-H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "navy 2022 bmw m3 hero shot, webp"}'{
"data": {
"params": {
"vehicle_id": "veh_93x1p7njkcsvq",
"make": "bmw",
"model": "m3",
"year": 2022,
"view": "front-3-4",
"color": "blue",
"format": "webp"
},
"display": {
"make_name": "BMW",
"model_name": "M3"
},
"candidates": [
{
"make_name": "BMW",
"make_slug": "bmw",
"model_name": "M3",
"model_slug": "m3",
"years": [
2022
],
"match_kind": "exact"
},
{
"make_name": "BMW",
"make_slug": "bmw",
"model_name": "M3 COMPETITION",
"model_slug": "m3-competition",
"years": [
2022
],
"match_kind": "prefix"
}
],
"confidence": "high",
"image_path": "/api/v1/images/car?make=bmw&model=m3&year=2022&view=front-3-4&color=blue&format=webp",
"extracted": {
"year": 2022,
"color": "blue",
"view": "front-3-4",
"size": null,
"width": null,
"format": "webp"
}
},
"request_id": "req_01j9x…"
}Send params.vehicle_id as vehicle= on the image request. confidence says how the name was read: high is the catalog's own name, or a reviewed alias of it, in the year asked for; medium is an interpretation (a badge, trim words, a body the catalog has no separate model for, a model name several makes share, a year the model does not have), so say which vehicle you chose; low is a guess, and candidates lists the options to show a user. extracted is what the phrase itself said: when extracted.year is not params.year, the catalog has no such model in the year asked for. A phrase that names nothing in the catalog answers 404. Agents get the same behavior through the resolve_vehicle tool.
Check a list of vehicles
/api/v1/vehicles/checkWhich of these 2,000 vehicles do you have? One call answers it. Send your vehicles as your data spells them and every entry is read exactly as an image request would read it, by the same rules, so the answer is what a render of that entry would do: serve a vehicle, or answer 404. Nothing is rendered and nothing is charged. Build a catalog from the matches, not by requesting every vehicle in every view and color and collecting the 404s.
| Parameter | Type | Description |
|---|---|---|
vehiclesrequired | array | 1 to 2,000 entries. Each is make, model and year, or vehicle (a stored id), plus an optional ref, under the spellings image requests take (brand for make, vehicle_id for vehicle, names in any case). Other fields are ignored, so the objects you send to /api/v1/image-urls can be checked as they are. |
vehicles[].make | string | The make as your data spells it (brand works too). |
vehicles[].model | string | The model as your data spells it: MX-5 Miata, F150, C300. |
vehicles[].year | integer | string | A four-digit model year. |
vehicles[].vehicle | string | A vehicle id you stored before, to confirm it; send it without make, model and year. |
vehicles[].ref | string | number | Your own id for the entry (a row, a SKU, a listing), echoed back with its answer. |
curl -X POST https://carimage.dev/api/v1/vehicles/check \
-H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vehicles": [
{"make": "Mazda", "model": "MX-5 Miata", "year": 2018, "ref": "sku-1041"},
{"make": "Lexus", "model": "TX 350", "year": 2022, "ref": "sku-1042"},
{"make": "Chevrolet", "model": "Sail", "year": 2022, "ref": "sku-1043"},
{"make": "Toyota", "model": "Corolla", "ref": "sku-1044"}
]}'{
"data": {
"summary": {
"total": 4,
"match": 1,
"suggestion": 1,
"miss": 1,
"invalid": 1,
"distinct_vehicles": 1
},
"results": [
{
"index": 0,
"ref": "sku-1041",
"status": "match",
"vehicle": {
"id": "veh_59854qbgfvar3",
"make": "mazda",
"make_name": "MAZDA",
"model": "mx-5",
"model_name": "MX-5",
"year": 2018,
"match": "alias",
"image_path": "/api/v1/images/car?vehicle=veh_59854qbgfvar3"
}
},
{
"index": 1,
"ref": "sku-1042",
"status": "suggestion",
"vehicle": null,
"reason": "year",
"suggestions": [
{
"make": "lexus",
"make_name": "LEXUS",
"model": "tx-350",
"model_name": "TX 350",
"year": 2024,
"id": "veh_8bbz514ey4ran",
"image_path": "/api/v1/images/car?vehicle=veh_8bbz514ey4ran"
},
{
"make": "lexus",
"make_name": "LEXUS",
"model": "nx350",
"model_name": "NX350",
"year": 2023,
"id": "veh_4nd9460kq2pvs",
"image_path": "/api/v1/images/car?vehicle=veh_4nd9460kq2pvs"
}
]
},
{
"index": 2,
"ref": "sku-1043",
"status": "miss",
"vehicle": null,
"reason": "model",
"suggestions": [],
"search": "/api/v1/vehicles?q=chevrolet%20sail"
},
{
"index": 3,
"ref": "sku-1044",
"status": "invalid",
"vehicle": null,
"error": "year is required (or send vehicle, a vehicle id)."
}
]
},
"request_id": "req_01j9x…"
}Every entry comes back in order, with its index and your ref, as one of four answers:
match: a render of the entry servesvehicle. Storevehicle.idand render by it.vehicle.matchis theX-Vehicle-Matchrule that read your spelling:exactfor a catalog spelling or an id, otherwise the looser rule (an alias, trim words, a badge, a typo), worth a glance before a backfill renders it.suggestion: the entry would be a404, andsuggestionsare the closest real vehicles: the same vehicle in its nearest catalog year, then models of the make that share a word with it or are spelled within a few letters of it. A suggestion is another vehicle, never substituted for yours: use one only when it is the car you mean.reasonsays what the catalog lacks: theyear, themodelor themake.miss: nothing in the catalog is close. Skip the vehicle in every view and color;searchis the catalog search to try by hand, and the request board or the open vehicle-db is how it gets added.invalid: the entry cannot be read (no year, both an id and names, a malformed id);errorsays what to fix. One bad row never fails the list.
summary counts each answer, and distinct_vehicles the different vehicles among the matches: what a backfill of the list counts against your plan's distinct vehicles a month. A check reads up to 2,000 entries (split a longer list; the SDK and the CLI do it for you), and an account can run 10 checks a minute. The suggestions come from the catalog's rules alone; the 404 of a single render also runs the catalog's fuzzy text search. Agents get the same check through the check_vehicles tool, 100 entries a call.
Backfill a catalog: map names to ids once
A marketplace, a dealer feed or a configurator already has its vehicles, spelled its own way. Map them to vehicle ids once, store the ids, and render by id from then on: no names to interpret, no 404 in any view or color, and each vehicle counted once against your plan.
- Export make, model and year as your data spells them, with your own row id as
ref. - Check the whole list: one command, one SDK call, or
POST /api/v1/vehicles/checkin chunks of 2,000. - Store
vehicle.idof every match beside your row. Ids never change, across deploys and catalog releases, so it is safe as a foreign key. - Decide the suggestions once: a person, or a rule of yours (for example, accept the same model in its nearest year for a model year the catalog does not carry yet). Store the id you chose.
- Render by id in every view and color you need (
vehicle=<id>, or signed URLs for pages). Skip the misses. - Check again only what did not match, after a catalog release or once a month: matches do not change, and a miss may since have been added.
# inventory.csv: make,model,year columns in any order (plus your own); ref is echoed back
npx @meterapp/car-image check inventory.csv --out inventory-checked.csv
# → the same file with status, vehicle_id, catalog_make, catalog_model, catalog_year,
# match, reason, suggested_vehicle_id and suggestions beside every rowimport { CarImageClient } from "@meterapp/car-image-sdk";
const client = new CarImageClient(); // reads CAR_IMAGE_API_KEY
// Your rows, any number: checkVehicles sends 2,000 per request and merges the answers.
const { data } = await client.checkVehicles(
rows.map((row) => ({ make: row.make, model: row.model, year: row.year, ref: row.id }))
);
for (const result of data.results) {
if (result.status === "match") await saveVehicleId(result.ref, result.vehicle!.id);
else if (result.status === "suggestion") await queueForReview(result.ref, result.suggestions);
}
// From then on, every picture names the vehicle by its id.
const { data: urls } = await client.createImageUrls(
["front-3-4", "side", "rear-3-4"].map((view) => ({ vehicle: row.vehicleId, view, color: "white", width: 800, format: "auto" })),
{ ttlSeconds: 604800 }
);How names are matched
make and model do not have to be the catalog's exact spelling. Every request is read by a fixed set of rules, from the most literal to the least, and the first rule that names exactly one catalog vehicle answers. A spelling the catalog carries always means itself. A rule that could name two different vehicles answers nothing, so you get a 404 with suggestions rather than a car you did not ask for. The rules are deterministic, need no network and never leave the catalog: what is served is always a real make, model and year.
| Capability | You send | Matches |
|---|---|---|
Accents, case and punctuation X-Vehicle-Match: exact | Citroën · MERCEDES BENZ · Model 3 | citroen · mercedes-benz · model-3 |
A unique prefix of a catalog model X-Vehicle-Match: prefix | all-american · x5-m | all-american-all-canada · x5-m-competition |
Hyphens added or dropped X-Vehicle-Match: separator | cx5 · santafe · f150 · 500-x · glc-350d-4matic | cx-5 · santa-fe · f-150 · 500x · glc-350-d-4matic |
A family without its word, or in another market's word X-Vehicle-Match: family | 3 (make=bmw) · gla · 3-serisi · c-klasse | 3-series · gla-class · 3-series · c-class |
Word order, language, the make in front X-Vehicle-Match: words | Série 3 · Serie 3 · 3er · Classe A · Clase GLC · q6-e-tron-sportback · bmw-x5 · mazda-3 | 3-series · 3-series · 3-series · a-class · glc-class · q6-sportback-e-tron · x5 · mazda3 |
A reviewed name for the same body X-Vehicle-Match: alias | rx-350 · camry-hybrid · transit-250 · 740 · sierra-2500hd · mx-5-miata | rx · camry · transit · 740i · sierra-hd · mx-5 |
Engine, gearbox, drivetrain, fuel and trim-level words X-Vehicle-Match: trim | tucson-n-line-s-t-gdi-hev-auto · x3-xdrive30d · tahoe-lt · golf-gtd-2-0-tdi-dsg | tucson · x3 · tahoe · golf-gtd |
An engine badge on a family or nameplate X-Vehicle-Match: badge | c300 · es350 · 760 · 745e | c-class · es · 7-series · 7-series |
A typo in a word of four letters or more X-Vehicle-Match: fuzzy | elentra · bettle · grand-cheroke · porshe (make) | elantra · beetle · grand-cherokee · porsche |
BMW badges keep their body style: 430-gran-coupe can match 430i-gran-coupe when that year carries it. After these rules miss, the catalog's reviewed aliases can resolve names such as maccane to macan and prado to land-cruiser-prado, reported as alias. This requires one catalog model in the requested make and year; ambiguous or unverified matches still return 404.
The same rules read make: nicknames and sub-brands (mercedes, chevy, vw, mercedes-amg), other separators (landrover), company words (kia-motors), a brand inside a longer name (vw-volkswagen), a unique prefix (aston) and a typo (porshe, cadilac, infinity). They apply to every surface that takes a make and model: GET /api/v1/images/car, signed URLs, 3D models, GET /api/v1/vehicles?make=&model= and the MCP tools.
What never matches. Words that change the body or name another vehicle are not read as trims, so these stay 404: body styles (corolla-hatchback, a4-avant, m8-gran-coupe, a Sportback in a year without a Sportback row), performance derivatives with their own bodywork (ct4-v, huracan-sto, x5-m, shelby-gt500), electric twins that share only a name (silverado-ev), long bodies (rx-350l), previous-generation carry-overs (1500-classic), and any model year the catalog does not carry for that vehicle — a 2027 request for a car whose catalog ends in 2026 is a 404 whose suggestions name 2026. A typo is one or two letters in a word of four letters or more; digits and short words (330i, x5, gti, model-s) must match exactly, because in car names they are identities.
Nothing is substituted silently. Every image response carries X-Vehicle-Match: exact when make and model were catalog slugs, otherwise the rule that interpreted them, in the order of the table (the less literal of the two when both were interpreted). X-Vehicle-Id and vehicle_id name the vehicle served. The slugs you sent stay in the URL, the filename and your usage. An integration that wants only literal spellings can refuse anything but exact, or send vehicle ids, which are never interpreted.
A vehicle is missing
A vehicle 404 says what to send instead. The problem document carries suggestions: up to five real catalog vehicles closest to your request, most useful first — the same vehicle in its nearest catalog year, models of the make that share a word with what you sent, then the catalog's own fuzzy matches, or the closest makes when the make is unknown — each with its id and image_path, and detail repeats them in prose. Retry with vehicle=<id>, or show them to your user.
{
"type": "https://carimage.dev/docs/errors#404",
"title": "Vehicle not found",
"status": 404,
"detail": "No model \"corolla-hatchback\" exists for TOYOTA in 2024. Closest catalog matches: toyota/corolla (2024), toyota/gr-corolla (2024), toyota/corolla-altis (2024), toyota/corolla-cross (2024), toyota/corolla-levin (2024). Search the catalog with GET /api/v1/vehicles?q=toyota%20corolla%20hatchback.",
"request_id": "req_01j9x…",
"catalog": "/api/v1/vehicles",
"suggestions": [
{
"make": "toyota",
"make_name": "TOYOTA",
"model": "corolla",
"model_name": "Corolla",
"year": 2024,
"id": "veh_8v5cs91hd9pxd",
"image_path": "/api/v1/images/car?vehicle=veh_8v5cs91hd9pxd"
},
{
"make": "toyota",
"make_name": "TOYOTA",
"model": "gr-corolla",
"model_name": "GR Corolla",
"year": 2024,
"id": "veh_…",
"image_path": "/api/v1/images/car?vehicle=veh_…"
}
]
}The catalog is open source. If a make, model or model year is really absent, add it to vehicle-db — the API picks up new releases, and every customer benefits.