Car Image API

Use cases · Insurance

Car images for insurance claims and quotes

Decode the VIN on a policy or claim for free, keep its vehicle id, and show one labelled view of the insured model in the claim file and the quote PDF.

A build guide written from the API's documented behavior; it names no customer. Published .

The problem: the record names a car nobody can see

A quote form, a policy record and a first notice of loss all name the vehicle the same way: by its VIN, typed by a customer or read off a registration document. Everything a person then sees about the car is text, a year, a make, a model and perhaps a trim. Asking "is this your vehicle?" next to a line of text invites a yes to anything.

A picture of the model the VIN decodes to lets the customer and the adjuster check at a glance that the record names the right kind of car. It cannot show which car: a render is a studio illustration of a catalog make, model and year, and it does not depict any specific physical vehicle. That one fact shapes the whole integration below.

What the integration looks like

  • Decode on intake. The quote flow or the policy system decodes the VIN once, for free, and stores the catalog's vehicle id with the policy. Claims opened against the policy inherit it.
  • One view everywhere. front-3-4, the hero angle, in one box on the quote screen, the claims workbench, the quote PDF and the policy portal, so every screen shows the model the same way.
  • Signed URLs, never the key. Browsers and PDF renderers load key-free URLs that your server mints.
  • Words for what a picture cannot say. Trim, options, the color on record, damage and mileage stay text and photos, and the picture says it is representative.

Step 1: decode the VIN, for free

GET /api/v1/vin/{vin} costs nothing and needs a key with images:read. It answers with the decoded fields from NHTSA's vPIC data (year, make, model, trim, series, body class, engine and every other attribute vPIC knows) and, when the catalog has the vehicle, a vehicle object with its stable id and a ready image_path.

Decode the VIN on the quote
curl --fail-with-body \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://carimage.dev/api/v1/vin/WP0AA2A96RS201834"
200 OK (trimmed)
{
  "data": {
    "vin": "WP0AA2A96RS201834",
    "valid": true,
    "errors": [],
    "suggested_vin": null,
    "year": 2024,
    "make": "Porsche",
    "model": "911",
    "trim": "Carrera, Carrera T, Carrera 4",
    "series": "Type 992",
    "body_class": "Coupe",
    "vehicle": {
      "id": "veh_78qtwrgh37bkr",
      "make": "Porsche",
      "model": "911",
      "year": 2024,
      "image_path": "/api/v1/images/car?vehicle=veh_78qtwrgh37bkr"
    },
    "source": "vpic-db"
  }
}

Read the trim: "Carrera, Carrera T, Carrera 4". This VIN's pattern covers three trims, so even the decode cannot say which one the policy insures, and a render of the 2024 911 certainly cannot. Ask the customer to confirm the trim, keep it with series and body_class as text, and let the picture show the model.

  • A VIN with a bad check digit still decodes, with valid: false and the problem in errors; suggested_vin marks the character vPIC thinks is wrong with !, which is what to show the person typing.
  • A form that has only the first characters can send a partial VIN: at least five characters, with * for each unknown position and an optional year.
  • A 404 means vPIC has no record of the pattern. vPIC covers vehicles made for the US market, so for other markets start from the make and model with POST /api/v1/images/resolve, which is free too.

Step 2: keep the vehicle id on the policy and the claim

Store data.vehicle.id on the policy as a plain string and copy it to every claim opened against it. The id is deterministic and permanent: the same vehicle has the same id on every deploy and after every catalog release, so it is safe as a foreign key, and every later request says vehicle=veh_78qtwrgh37bkr instead of spelling a make and a model.

When data.vehicle is null the decoded make and model are not in the catalog, as with trailers and some commercial chassis. The decoded fields are still yours to show. Show them without a picture, and never put a similar car in its place.

Step 3: one view on the claim file and the quote PDF

The quote screen, the claims workbench and the policy portal are browser apps, so your server mints signed URLs with POST /api/v1/image-urls: 1 credit per URL when it is minted, up to 50 per call, and free to load until it expires. Frame the picture once and reuse the shape: front-3-4 in a 640×400 box with fit: "contain", and trim with a little padding so a coupe and a pickup fill the box the same way. How long each URL should live depends on where it is shown:

  • The claims workbench. Mint when the screen draws. The default lifetime of 1 hour covers a working session, and a claim reopened tomorrow gets a fresh URL.
  • The quote PDF. When the PDF is rendered from HTML, the renderer loads the picture once and embeds it, so the default lifetime is plenty, and the PDF service never holds your key. format: "jpg" flattens the picture onto white, which is what a document wants.
  • The policy portal and the renewal email. These outlive any lifetime, so mint with renew: true and ttl_seconds: 604800 (7 days). The URL keeps working, each further week in which it is opened costs 1 credit, and renew_days: 365, the longest allowed, covers a year-long policy term.

Give the key that mints the URLs a list of allowed origins on the dashboard: a URL loaded from another site answers 403 with origin_not_allowed, while email clients and PDF viewers, which send no Referer, still load it. Revoking the key ends every URL it minted.

Intake, the workbench, the PDF and the portal, with the TypeScript SDK
import { CarImageClient, type ImageParams } from "@meterapp/car-image-sdk";

const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });

// Quote or policy intake: the VIN in, the vehicle id and the decoded words on the record. Free.
const { data: decoded } = await client.decodeVin(policy.vin);
policy.vehicleId = decoded.vehicle?.id ?? null; // null: show the decoded text and no picture
policy.trimText = decoded.trim;                 // "Carrera, Carrera T, Carrera 4": words, never the picture

if (policy.vehicleId) {
  // One view, framed once, for every screen and document that shows this policy's car.
  const view: ImageParams = {
    vehicle: policy.vehicleId, view: "front-3-4", color: policy.paintHex ?? "silver",
    width: 640, height: 400, fit: "contain", trim: true, padding: 6,
  };

  // The claims workbench: a URL for this session, minted when the screen draws.
  const { data: [workbench] } = await client.createImageUrls({ ...view, format: "auto" });

  // The quote PDF: the HTML-to-PDF renderer loads the URL once and embeds the picture.
  const { data: [pdf] } = await client.createImageUrls({ ...view, format: "jpg" });

  // The policy portal and the renewal email: one URL that keeps working for the term.
  const { data: [portal] } = await client.createImageUrls(
    { ...view, format: "auto" },
    { ttlSeconds: 604800, renew: true, renewDays: 365, idempotencyKey: `policy:${policy.id}:image` }
  );
}

Step 4: label it as the model, never as the insured car

Put the label where the picture is, not only in a footnote: in the alt text and in a caption under the image. The render shows the make, model and year in a paint you choose, and color can follow the color on the policy (a preset name or any hex). It is still the model in that paint, not the customer's car.

A quote's vehicle block
<figure>
  <img src="https://carimage.dev/api/v1/delivery/eyJhbGciOi…" width="640" height="400"
       alt="Representative image of a 2024 Porsche 911, not a photo of the insured vehicle">
  <figcaption>
    2024 Porsche 911 · Coupe · Type 992 · trim on the VIN: Carrera, Carrera T or Carrera 4
    <small>Representative image of the model. Not a photo of the insured vehicle.</small>
  </figcaption>
</figure>

What it costs, and which plan fits

  • Free: the VIN decode, resolve, catalog lookups, and every load of a signed URL within its lifetime.
  • 1 credit: each picture delivered to your server or each URL minted, and each further week in which a renewing URL is opened.
  • Distinct vehicles: a quote funnel sees many different vehicles, and each plan caps the distinct vehicles a month: Free 100, Pro 2,500 and Business 15,000; Enterprise has no cap. A vehicle already served this month is always allowed, and a request past the cap is refused before anything is charged, so size the plan to the vehicles you quote, not to the credits.

On a paid plan the license covers a render inside what your product produces for its users, a quote or a report included, for as long as the plan is active; the details are in the license.

CLI, SDK and MCP

npx @meterapp/car-image
# decode for free and keep the id
npx @meterapp/car-image vin WP0AA2A96RS201834 --json | jq -r .data.vehicle.id

# one renewing URL for the policy's documents, minted once however often intake retries
npx @meterapp/car-image url --vehicle veh_78qtwrgh37bkr --view front-3-4 --width 640 --height 400 \
  --fit contain --trim --padding 6 --format auto --ttl 604800 --renew --renew-days 365 \
  --idempotency-key policy-88213-image
  • SDK: client.decodeVin(vin, { year? }) and client.createImageUrls(…), as above; client.resolve(query) for a vehicle without a VIN.
  • MCP: decode_vin and create_car_image_urls are in the core toolset, so an assistant with the claim in context can answer "show me the insured model" from the VIN on file. Setup is on the MCP page.

Frequently asked questions

Can the image stand in for photos of the insured vehicle?
No. It is a studio render of the catalog's make, model and year, not a photograph, and it depicts no specific vehicle. It helps a customer or an adjuster see that the record names the right kind of car. Condition, damage, trim, options and modifications come from photos, inspections and the decoded text, and the image should say it is representative.
What does a picture on every quote cost?
The VIN decode is free. The picture costs 1 credit when its signed URL is minted, or when its bytes are delivered to your server; loading a signed URL is free until it expires. A renewing URL costs 1 credit more for each week in which it is opened, and nothing for the weeks nobody looks.
The VIN decodes but vehicle is null. Now what?
The decoder knew the vehicle but the catalog has no entry for that make, model and year, which happens with trailers and some commercial chassis. Show the decoded fields without a picture and never substitute a similar car. Search the catalog in case the spelling differs, and if it really is missing, ask for it on the public requests board.
Does VIN decoding work for vehicles from outside the US market?
The decoder is NHTSA's vPIC data, which covers vehicles made for the US market, so a correctly typed VIN from another market can answer 404 or decode without a model. Find that car by make, model and year with the free resolve endpoint instead; the catalog itself covers other markets' nameplates.
How long may a claim file keep the picture?
While your plan is active. On a paid plan the license covers a render inside what your product produces, a quote or a report included; it ends 30 days after the plan ends, and copies must then be deleted, including those inside stored documents. A longer right needs an Enterprise agreement: support@carimage.dev. Renders can also be improved over time, so a record that must keep exactly what the customer saw should store the file rather than a URL.

Keep reading

Customer story · Dealer softwareHow to turn a VIN or a stock sheet into a photo for every vehicle in dealer softwareA stock record has a VIN and a stock number long before it has a photo. Dealer tools decode the VIN for free, keep the vehicle id on the record, and show one consistent view in the CRM through signed URLs.