Market & listings · Vehicle Photos

Car Images API

Give it a VIN, get back the photo gallery for that vehicle's best current listing in the source's own order, the number of photos the listing advertises, and a link back to that listing. It exists so a VIN-driven flow — a trade-in quote, an appraisal screen, a chat answer — can show the actual car instead of a grey placeholder. Gallery coverage is partial and the response says so: compare galleryCount against photoCount, and that constraint is the first thing you should know about it.

GET/v1/vehicles/photos/{vin}5 s route budget

Get an API keyFull parameter reference

What comes back

photos (the gallery in the source's own order, photos[0] being the hero), primaryImage (that same hero URL, never null on a 200), galleryCount (how many URLs we actually returned), photoCount (how many the source listing advertises, nullable), galleryComplete (true only when those two agree), listingSource (the scrape source, e.g. "autolist"), listingUrl (the vehicle detail page on the source marketplace), source "carscrape" and the upper-cased vin. A real response for a 2026 Ford F-150 might return three cargurus-hosted JPEGs against photoCount 39 and galleryComplete false. That gap is the thing to build for: size your carousel from galleryCount or photos.length, never from photoCount, and treat galleryComplete false as "there are more behind listingUrl".

Request
curl -sS https://api.vehicles.dev/v1/vehicles/photos/1FTFW3L57TKD09376 \
  -H "Authorization: Bearer $VEHICLES_API_KEY" \
  -H "Accept: application/json"
200 · partial gallery for a 2026 Ford F-150
{
  "galleryComplete": false,
  "galleryCount": 3,
  "listingSource": "autolist",
  "listingUrl": "https://www.autolist.com/ford-f+150#vin=1FTFW3L57TKD09376",
  "photoCount": 39,
  "photos": [
    "https://static.cargurus.com/images/forsale/2026/07/18/07/13/2026_ford_f-150-pic-6182805071162814707-1024x768.jpeg",
    "https://static.cargurus.com/images/forsale/2026/07/18/07/13/2026_ford_f-150-pic-3355019284471120388-1024x768.jpeg",
    "https://static.cargurus.com/images/forsale/2026/07/18/07/13/2026_ford_f-150-pic-8821004417720935512-1024x768.jpeg"
  ],
  "primaryImage": "https://static.cargurus.com/images/forsale/2026/07/18/07/13/2026_ford_f-150-pic-6182805071162814707-1024x768.jpeg",
  "source": "carscrape",
  "vin": "1FTFW3L57TKD09376"
}

The complete parameter table, the response-field dictionary and the per-endpoint error codes live in the reference: full parameter reference for /v1/vehicles/photos/{vin}.

What it is actually built on

The image comes from our listings table, not from a manufacturer or stock-photography library. For a given VIN we select that VIN's best current listing that actually has an image — active listings win, and among those, the most recently ingested one. That means the photo is the real car (or at least the real listing) rather than a studio render of the trim, which is the right trade-off for verification and identity-confirmation flows and the wrong one if you wanted clean marketing imagery. The URLs we return are CDN links owned by the source marketplace; we do not rehost, resize, watermark or proxy them. The route upper-cases the VIN and requires exactly 17 VIN-safe letters or digits; the backing validator also rejects a bad North American check digit. It runs on a 5-second upstream budget — a valid VIN with no matching image 404s with photos_not_found, and because billing is success-only, that costs nothing.

Data source
The listings table. We pick the VIN's best current listing that has an image — active listings win, then the most recently ingested. Image URLs are CDN links owned by the source marketplace and are not rehosted by us.
Route budget
5 s
Billed per
successful lookup, successful responses only
MCP tool
get_vehicle_photos

Limits: what this endpoint will not do

Gallery coverage is partial and you must check it per response. photoCount is the count the SOURCE advertised; galleryCount is how many URLs we can actually serve; galleryComplete is true only when those agree. They disagree in two ordinary cases: the source published a count without exposing the gallery, and the listing was last crawled before gallery capture shipped. Coverage grows as listings are re-crawled, so an empty photos array with a non-zero photoCount means "we do not hold these URLs", never "this car has no photos" — follow listingUrl for the rest. A VIN that appears in Car Listings API results can still 404 here, because the matched listing row may have no primary image; a 404 means "no listing with an image", not "no such vehicle". Lookups are VIN-keyed only: there is no year/make/model image endpoint, so you cannot get a picture of a 2023 Camry XSE in the abstract, only of specific VINs that have appeared in listings we scraped. The URLs point at third-party CDNs and will rot when the underlying listing is taken down — treat them as ephemeral and mirror anything you need to keep, ideally with a fallback in your UI for a 404 on the image itself. Rights in the images belong to the source marketplace or dealer, not to us; we return a pointer, and you should confirm your own use is permitted before republishing. Coverage is US listings only. The endpoint is available now and successful responses use the published catalog rate. Standard plans include no SLA.

What teams build with it

Thumbnail on an instant-offer or trade-in quote screen

A lender, insurer or instant-offer buyer takes the VIN the customer typed, calls VIN decode for identity and this endpoint for the image, and renders "Your 2022 Toyota Highlander Limited" next to a real photo of a matching listing. It measurably increases confidence that the system understood which car the customer has, at $0.0009 per lookup on Pro — cheaper than most placeholder-avoidance workarounds.

Visual verification inside a dealer CRM or inventory sync

When ingesting third-party inventory or reconciling a feed, pull the hero image for each VIN and surface it next to the decoded identity. A mismatch between the decoded body style or color and the photo is a fast, human-checkable signal that a row is mis-keyed — far quicker than auditing field by field. Pair with the data_quality score from listings search to triage which rows to look at.

Illustrated answers in an AI car-shopping assistant

The MCP server exposes this as the get_vehicle_photos tool, so an agent that has just answered what a VIN is worth can follow up with what it looks like and a link to the source listing in the same turn. Because listingUrl comes back with the image, the agent can attribute and deep-link rather than presenting an orphaned photo.

Marketplace listing prefill and QA

For a marketplace onboarding private sellers or small dealers by VIN, the hero image plus listingUrl is a useful prefill and duplicate-detection signal: if the VIN a seller is posting already has a live listing elsewhere with a photo, you know before you publish. Use it as a check, not as the seller's own listing imagery.

Integrating it properly

The reference ships the bare curl. This is the shape worth deploying: a typed client that branches on the RFC 9457 code slug rather than the human-readable title, and retries only the codes this endpoint marks retryable.

TypeScript
const API = "https://api.vehicles.dev";

// Only these codes are marked retryable for this endpoint. Everything else is terminal.
const RETRYABLE = new Set(["photos_upstream_unavailable", "photos_upstream_error"]);

export async function getVehiclePhotos(vin: string, attempt = 0): Promise<GetVehiclePhotosResponse> {
  const response = await fetch(
    `${API}/v1/vehicles/photos/${encodeURIComponent(vin)}`,
    {
      headers: {
        Accept: "application/json",
        Authorization: `Bearer ${process.env.VEHICLES_API_KEY ?? ""}`
      },
      // The route budget is 5 s; allow it plus headroom.
      signal: AbortSignal.timeout(10_000)
    }
  );

  if (response.ok) return (await response.json()) as GetVehiclePhotosResponse;

  // Failures are application/problem+json. Branch on `code`, never on the human-readable title.
  const problem = (await response.json()) as { code: string; detail?: string };
  if (RETRYABLE.has(problem.code) && attempt < 3) {
    await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
    return getVehiclePhotos(vin, attempt + 1);
  }
  throw new Error(`${response.status} ${problem.code}: ${problem.detail ?? ""}`);
}

How to check it is behaving

The route enforces a 5-second upstream budget, so a slow dependency returns a problem document instead of holding your request open. Exactly 2 codes are marked retryable here — photos_upstream_unavailable and photos_upstream_error — and everything else documented for this endpoint is terminal, so backoff on anything else just burns your rate limit. Billing is success-only: failed calls release their reservation, so you can measure error rates and hold-out accuracy against your own data without paying for the failures. Usage is visible per endpoint in the dashboard.

What this one endpoint costs

Priced per successful lookup. The platform fee buys throughput and endpoint access; the unit price covers the data work. Worked at 100,000 successful calls a month so you can see where the plans cross over — the full price matrix is in the reference.

Car Images API pricing by plan
PlanPlatform feeRate limitPer successful lookup100,000 / month
Starter$0 / mo5 rps$0.001$100$100 usage + platform fee
Pro$299 / mo10 rps$0.0009$389$90 usage + platform fee
Scale$599 / mo50 rps$0.0007$669$70 usage + platform fee

Eligible for the Starter plan's included monthly calls; past those it draws on your credit balance. Data fees are prepaid from credits on every plan: when included calls and credits cannot cover a call you get 402 insufficient_credits rather than an overage bill. Published rates are active and apply only to successful responses. Standard plans include no SLA.

Questions engineers actually ask

Can I get all the photos for a VIN, or just one?

As many as the source published and gave us. photos is the gallery in the source's own order, with photos[0] repeated as primaryImage. Read galleryComplete before you rely on it: when it is false, galleryCount is smaller than the photoCount the listing advertised, either because the source withheld the gallery or because the listing has not been re-crawled since gallery capture shipped. In that case follow listingUrl to the source marketplace for the remainder — that link is in the response for exactly this reason.

Why did a VIN that shows up in listings search return 404 here?

Because the listing row we matched has no primary image. Listings and photos are drawn from the same store, but image presence varies by source and by individual listing. A 404 photos_not_found is a normal, expected outcome — handle it as "no photo available" in your UI rather than as an error. It is also free: success-only billing means the failed lookup releases the reservation.

Do the image URLs expire?

They can, and you should assume they will. The URLs point at CDNs owned by the source marketplaces, and when a listing comes down the asset often goes with it. There is no guaranteed lifetime. If the image matters to a record you keep — a saved quote, an audit trail, a generated PDF — fetch and store your own copy at the time you generate it, subject to the rights question below.

Can I use these images in my own product?

We return a link to an image hosted and owned by the source marketplace or dealer; we do not own it and cannot grant you rights to it. Displaying it in-product with attribution and a link through to listingUrl is the pattern the endpoint is shaped for. Rehosting, watermarking or redistributing the imagery is between you and the rights holder — and our standard plans cover use inside your product, not redistribution of raw records.

What does it cost?

It is the lowest-priced of the data endpoints and is eligible for Starter's 1,000 included monthly calls, then $0.001 per successful lookup on Starter, $0.0009 on Pro and $0.0007 on Scale. 100,000 lookups a month is $90 on Pro or $70 on Scale. Only successful lookups are billed, so the 404s you will inevitably hit do not appear on the invoice.

Can I look up an image by year, make and model instead of a VIN?

No. The path takes a VIN and the selection logic is this VIN's best listing that has an image. There is no year/make/model image lookup, and we do not carry stock or studio photography. If you need generic trim imagery for a configurator, this is the wrong source; if you need to show the actual car behind a VIN, it is the right one.

Where to go next

Most integrations chain two or three of these. The ones that pair with car images api most often:

The reference entry for this endpoint — Vehicle Photos in the API documentation — carries the parameter constraints, defaults, response fields and the full error table with retryable flags. Agents can reach the same endpoint as the get_vehicle_photos tool on the vehicles-dev-mcp MCP server, and the machine-readable contract is published at https://api.vehicles.dev/openapi.json.

Browse the rest of the catalog on the Vehicle data APIs hub.