Car Image API
Documentation

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.

GET/api/v1/vehicles/{id}
nonefree
bash
curl "https://carimage.dev/api/v1/vehicles/veh_395yw8tn73ff8"
200 OK
{
  "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:

text
https://carimage.dev/api/v1/images/car?vehicle=veh_395yw8tn73ff8&view=side&color=1a2b3c&w=600&h=400&trim=1

List years, makes and models

GET/api/v1/vehicles
images:readfree
ParameterTypeDescription
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:

bash
# 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" }, …] } }

Look a vehicle up from free text

POST/api/v1/images/resolve
account:readfree

The 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.

ParameterTypeDescription
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.
bash
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"}'
200 OK
{
  "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

POST/api/v1/vehicles/check
images:readfree

Which 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.

ParameterTypeDescription
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.
bash
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"}
  ]}'
200 OK
{
  "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 serves vehicle. Store vehicle.id and render by it. vehicle.match is the X-Vehicle-Match rule that read your spelling: exact for 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 a 404, and suggestions are 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. reason says what the catalog lacks: the year, the model or the make.
  • miss: nothing in the catalog is close. Skip the vehicle in every view and color; search is 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); error says 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.

  1. Export make, model and year as your data spells them, with your own row id as ref.
  2. Check the whole list: one command, one SDK call, or POST /api/v1/vehicles/check in chunks of 2,000.
  3. Store vehicle.id of every match beside your row. Ids never change, across deploys and catalog releases, so it is safe as a foreign key.
  4. 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.
  5. Render by id in every view and color you need (vehicle=<id>, or signed URLs for pages). Skip the misses.
  6. 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.
CLI
# 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 row
SDK
import { 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.

Spellings the image endpoints read, and what they match
CapabilityYou sendMatches
Accents, case and punctuationX-Vehicle-Match: exact
Citroën · MERCEDES BENZ · Model 3citroen · mercedes-benz · model-3
A unique prefix of a catalog modelX-Vehicle-Match: prefix
all-american · x5-mall-american-all-canada · x5-m-competition
Hyphens added or droppedX-Vehicle-Match: separator
cx5 · santafe · f150 · 500-x · glc-350d-4maticcx-5 · santa-fe · f-150 · 500x · glc-350-d-4matic
A family without its word, or in another market's wordX-Vehicle-Match: family
3 (make=bmw) · gla · 3-serisi · c-klasse3-series · gla-class · 3-series · c-class
Word order, language, the make in frontX-Vehicle-Match: words
Série 3 · Serie 3 · 3er · Classe A · Clase GLC · q6-e-tron-sportback · bmw-x5 · mazda-33-series · 3-series · 3-series · a-class · glc-class · q6-sportback-e-tron · x5 · mazda3
A reviewed name for the same bodyX-Vehicle-Match: alias
rx-350 · camry-hybrid · transit-250 · 740 · sierra-2500hd · mx-5-miatarx · camry · transit · 740i · sierra-hd · mx-5
Engine, gearbox, drivetrain, fuel and trim-level wordsX-Vehicle-Match: trim
tucson-n-line-s-t-gdi-hev-auto · x3-xdrive30d · tahoe-lt · golf-gtd-2-0-tdi-dsgtucson · x3 · tahoe · golf-gtd
An engine badge on a family or nameplateX-Vehicle-Match: badge
c300 · es350 · 760 · 745ec-class · es · 7-series · 7-series
A typo in a word of four letters or moreX-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.

404 Not Found
{
  "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.