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".
# 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"}// 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=sidefor the cards: every car faces the same way on the same baseline. Each left-side angle has a-righttwin (side-right), so the cards can face into the layout. The class page usesfront-3-4, the hero angle.w=640&h=320&fit=contain: exactly 640×320 for every class, the whole car visible. The response'sX-Image-WidthandX-Image-Heightconfirm 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=f4f4f4flattens 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=webpfor stored files;format=autoon signed URLs, which serves WebP or PNG to each browser.
# 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.webpStep 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/carand 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'sETag: a nightly re-check withIf-None-Matchanswers304and costs nothing. For the fleet above that is 8 credits, once. - Or mint signed URLs once and reuse them.
POST /api/v1/image-urlstakes 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. Addrenew: trueand 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.
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.
// 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 itCLI, SDK and MCP
- CLI:
npx @meterapp/car-image get --vehicle veh_… --view side --width 640 --height 320 --fit contain --trim --padding 6 --out card.webpwrites a card to a file;car-image url --batch classes.json --renewmints a batch of URLs. - SDK:
client.vehicles({ make, model })for the ids,client.getImage(…)for files,client.createImageUrls(…)for URLs. - MCP:
resolve_vehicleandcreate_car_image_urlslet 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.