Car Image API

Use cases · Car rental

Car images for rental fleet pages

Show every rental class the same way: one representative model per class, identical framing with trim, padding and fit, one named paint, and URLs you cache.

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

The problem: you rent a class, not a car

A rental customer books a class, a compact, a midsize SUV, a minivan, and drives away in whichever car of that class is on the lot. The booking page still needs a picture for every class, and the convention is one representative model with the words "or similar".

Those pictures tend to come from wherever each one could be found: a press shot here, a dealer photo there, different angles, different backdrops, cars that fill their cards differently. A grid assembled that way looks assembled. What a fleet page wants is the opposite: every class drawn the same way, so the only thing that changes from card to card is the car.

What the integration looks like

  • One representative model per class, kept as a catalog vehicle id, with "or similar" in the label and never in the picture.
  • One request shape for every card: the same view, box, fit, crop, padding, paint, backdrop and format.
  • Two pictures per class at most: the card in the grid and the hero on the class page.
  • Cached, because the grid is the same for every visitor: files on your CDN, or signed URLs minted once and reused.

Step 1: pick a representative model per class, by id

Choose the model each class is sold under and look up its current model year. GET /api/v1/vehicles?make=&model= is free and public and lists every year of one model, each with the stable vehicle id you will store; POST /api/v1/images/resolve does the same from a phrase such as "2026 toyota rav4".

The id of one model year
# one model's years, each with its vehicle id (free, public)
curl -s "https://carimage.dev/api/v1/vehicles?make=toyota&model=rav4" | jq -c '.data.vehicles[] | select(.year == 2026)'
# {"year":2026,"id":"veh_e5w75c93f6cvk"}
fleet.ts
// One representative model per class: its current model year, by catalog id.
// "or similar" lives in the label; the picture never promises the car on the lot.
export const CLASS_IMAGES = {
  compact:        { vehicle: "veh_fy7r2yvwt4p1v", label: "Toyota Corolla or similar" },    // 2026 Toyota Corolla
  "midsize-suv":  { vehicle: "veh_e5w75c93f6cvk", label: "Toyota RAV4 or similar" },       // 2026 Toyota RAV4
  "fullsize-suv": { vehicle: "veh_9mzh9ejqjye43", label: "Chevrolet Tahoe or similar" },   // 2026 Chevrolet Tahoe
  minivan:        { vehicle: "veh_9jck7rrybf35p", label: "Chrysler Pacifica or similar" }, // 2026 Chrysler Pacifica
};

The ids never change, so this map is the only place a fleet change touches: when the midsize class moves to another model, or to the next model year, replace one id and every card, hero and email that reads the map follows.

Step 2: one request shape for every class

The 8 camera views are framed identically for every vehicle in the catalog, which is what makes a grid line up. The rest of the card is a handful of parameters, decided once:

  • view=side for the cards: every car faces the same way on the same baseline. Each left-side angle has a -right twin (side-right), so the cards can face into the layout. The class page uses front-3-4, the hero angle.
  • w=640&h=320&fit=contain: exactly 640×320 for every class, the whole car visible. The response's X-Image-Width and X-Image-Height confirm the canvas.
  • trim=1&padding=6: crop to the car before fitting it, with 6% of its longer side as a margin, so a Corolla and a Tahoe fill their cards the same way instead of floating in the square source frame.
  • background=f4f4f4 flattens the card onto the grid's own gray. Leave it out to keep the transparent background and let the card's color show through.
  • format=webp for stored files; format=auto on signed URLs, which serves WebP or PNG to each browser.
A card and a hero for one class
# the class card: the same box, view, crop, paint and backdrop for every class
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://carimage.dev/api/v1/images/car?vehicle=veh_e5w75c93f6cvk&view=side&color=white&w=640&h=320&fit=contain&trim=1&padding=6&background=f4f4f4&format=webp" \
  -o midsize-suv-card.webp

# the class page's hero: the three-quarter view, the full width the API delivers
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://carimage.dev/api/v1/images/car?vehicle=veh_e5w75c93f6cvk&view=front-3-4&color=white&w=1024&format=webp" \
  -o midsize-suv-hero.webp

Step 3: one named paint for the whole grid

The 15 presets are named paints: white, black, gray, silver, blue, red, green, brown, beige, tan, orange, yellow, gold, burgundy, purple. The default is silver, and grey reads as gray. Pick one for every class so the cards read as a set; white is shown above. Each preset keeps its own paint swatch rather than the CSS color of the same name (red is #b3151b), and every paint, a hex included, is a recolor of one neutral render, so it costs the same 1 credit.

Step 4: cache the files or the URLs

The class grid is identical for every visitor, so it should cost next to nothing per visit. Two ways get there:

  • Store the files. Fetch each card and hero once with GET /api/v1/images/car and serve them from your CDN like any other asset; a paid plan allows copies on your own servers to serve your product while it is active. Keep each file's ETag: a nightly re-check with If-None-Match answers 304 and costs nothing. For the fleet above that is 8 credits, once.
  • Or mint signed URLs once and reuse them. POST /api/v1/image-urls takes up to 50 images per call and returns key-free URLs that live up to 7 days. A URL with unlimited uses is publicly cacheable within its lifetime, so when every visitor loads the same URL, repeats come from the CDN for free. Add renew: true and a stored URL keeps working past its lifetime, at 1 credit per image for each further week in which it is loaded: at most 8 credits a week for the fleet above.

Restrict the key that mints the URLs to your booking domain on the dashboard, and a URL copied onto another site answers 403.

The grid's URLs, minted once, with the TypeScript SDK
import { CarImageClient, type ImageParams } from "@meterapp/car-image-sdk";
import { CLASS_IMAGES } from "./fleet";

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

// Framed once, for every class.
const card = (vehicle: string): ImageParams => ({
  vehicle, view: "side", color: "white", width: 640, height: 320,
  fit: "contain", trim: true, padding: 6, background: "#f4f4f4", format: "auto",
});
const hero = (vehicle: string): ImageParams => ({ vehicle, view: "front-3-4", color: "white", width: 1024, format: "auto" });

// Every visitor sees the same grid: mint the URLs once and serve the stored ones.
// renew keeps each URL working past its 7-day lifetime, one credit per image
// for each further week in which it is loaded.
const classes = Object.entries(CLASS_IMAGES);
const { data } = await client.createImageUrls(
  classes.flatMap(([, entry]) => [card(entry.vehicle), hero(entry.vehicle)]),
  { ttlSeconds: 604800, renew: true, idempotencyKey: "class-grid-2026-10" }
);
await saveClassImages(
  classes.map(([key], index) => ({ key, card: data[2 * index].url, hero: data[2 * index + 1].url, renewsUntil: data[2 * index].renews_until }))
);

Step 5: the booking confirmation

The confirmation email shows the class that was booked, and emails are opened whenever the renter gets to them. Mint its picture as a renewing URL: ttl_seconds at the 7-day maximum, renew: true, and renew_days running past the return date. Weeks in which nobody opens the email cost nothing, and after the rental the URL stops renewing. An Idempotency-Key per booking makes a retried send replay the first URL instead of minting a second, and storing the URL on the booking lets every later resend reuse it. Our own lifecycle emails work this way; the email story has the mechanism and the HTML.

A renewing URL for one confirmation
// The booking confirmation: opened today, again at the counter, maybe never.
const { data: [classImage] } = await client.createImageUrls(card(booking.classVehicle), {
  ttlSeconds: 604800,
  renew: true,
  // A week past the return date, and no renewals after it (365 days at most).
  renewDays: Math.min(daysUntil(booking.returnDate) + 7, 365),
  // A retried send replays the first URL instead of minting a second one.
  idempotencyKey: `booking:${booking.id}:class`,
});
booking.classImageUrl = classImage.url; // later resends reuse it

CLI, SDK and MCP

  • CLI: npx @meterapp/car-image get --vehicle veh_… --view side --width 640 --height 320 --fit contain --trim --padding 6 --out card.webp writes a card to a file; car-image url --batch classes.json --renew mints a batch of URLs.
  • SDK: client.vehicles({ make, model }) for the ids, client.getImage(…) for files, client.createImageUrls(…) for URLs.
  • MCP: resolve_vehicle and create_car_image_urls let an assistant draft a class card from "midsize SUV, RAV4 or similar". Setup is on the MCP page.

Frequently asked questions

Can the card show the exact car the renter will get?
No, and it should not try. A class is rented as “or similar”, and a render depicts a catalog make, model and year, not a car on your lot. Show one representative model with “or similar” in the label. Once a specific car is assigned at the counter, its VIN decodes for free to its catalog vehicle if you want to show that model instead.
Why are the cars in the grid not to scale?
Because trim crops each render to the car and fit=contain fills the card with it, so a compact and a full-size SUV fill their cards the same way. That is what keeps a grid tidy. Say the size in words: the class name, the seats and the bags.
What does the class grid cost?
A fleet of 4 classes with a card and a hero each is 8 credits. Store the files on your CDN and that is the whole bill, with free re-checks by ETag. Serve renewing signed URLs instead and it is at most 8 credits a week, only for the weeks in which the pages are opened.
Which paint should the grid use?
One preset for every class, so the cards read as a set. The 15 presets are white, black, gray, silver, blue, red, green, brown, beige, tan, orange, yellow, gold, burgundy, purple, and the default is silver; any hex works too, at the same price. The renter gets the color that is on the lot, which the card never promised.
What if a class's model is not in the catalog?
A vehicle that is not there answers 404 with suggestions: the closest catalog vehicles, each with its id. Use one of them, or ask for the vehicle on the public requests board. The catalog is open source.

Keep reading

Customer story · Developer toolsHow to embed car images in email with signed URLs that never breakOur own lifecycle emails embed each customer's exact first render as a live signed URL that renews itself for a year. Here is how that works and how to copy it.