Availability & quality
Which views are ready and at which quality (free); upgrade a stored view to high quality for 20 credits.
Every image is drawn once and then reused. This page explains what that first drawing involves, why it takes a little while, how to see for free which images of a vehicle are already ready, and how to raise any of them to high quality.
How a car gets its paint
We do not draw a new picture for every color. We draw each view of a car once, in a neutral paint, and then our own paint tools trace a precise overlay of that picture: a map of every painted panel, kept apart from the glass, tyres, lights and trim. With that overlay, the same picture can be repainted in any of the preset colors or any hex color in a fraction of a second, and every color of a view is the very same car.
Making the picture and its paint overlay is the slow part: usually 36–39 seconds, and nine times in ten under a minute. It happens once per view of a vehicle, for everyone. After that, every color, size, background, crop and shadow of that view answers at once.
Standard and high quality
A new picture is made at standard quality, so the first image comes back as fast as possible and you can show it to your customer right away. Any stored view can then be raised to high quality on request, for 20 credits. The upgrade replaces the stored picture in place: the same URLs and signed URLs keep working, and every color and size of that view comes back at high quality from then on. It also takes about half a minute to a minute, so it is a separate call you make once, not a parameter of every request.
See what is ready (free)
/api/v1/images/availability| Parameter | Type | Description |
|---|---|---|
vehicle | string | A stable vehicle id ( veh_…), or send make, model and year as on /docs/images. |
view | string | One view to report. All eight when absent. |
color | string | Optional. Almost every view answers every color once it is ready; this only matters for a few older renders stored in one color. |
curl --fail-with-body \
-H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
"https://carimage.dev/api/v1/images/availability?vehicle=veh_errc87t1jgata"{
"data": {
"object": "image_availability",
"vehicle": {
"id": "veh_errc87t1jgata",
"make": "toyota",
"model": "camry",
"year": 2025
},
"color": "silver",
"views": [
{
"view": "front-3-4",
"cached": true,
"quality": "high",
"upgradable": false,
"upgrade_in_progress": false,
"render_eta": null,
"upgrade_eta": null
},
{
"view": "side",
"cached": true,
"quality": "standard",
"upgradable": true,
"upgrade_in_progress": false,
"render_eta": null,
"upgrade_eta": {
"typical_seconds": 35,
"slow_seconds": 60,
"samples": 0
}
},
{
"view": "rear",
"cached": false,
"quality": null,
"upgradable": false,
"upgrade_in_progress": false,
"render_eta": {
"typical_seconds": 39,
"slow_seconds": 55,
"samples": 4213
},
"upgrade_eta": null
},
"…"
],
"summary": {
"cached": 6,
"high_quality": 1,
"upgradable": 5
},
"estimates": {
"first_render": "…",
"new_view": "…",
"upgrade_master": "…",
"upgrade_view": "…"
},
"pricing": {
"image_credits": 1,
"upgrade_credits": 20
}
},
"request_id": "req_01j9x…"
}cached: the view answers at once, in any color and size. It still costs 1 credit to deliver, like every image.quality:standardorhigh;nullwhen the view is not ready.render_eta: for a view that is not ready, how long its first request takes.typical_secondsis the median andslow_secondsthe time nine in ten finish within, both measured on the last 3 days of real requests (samplessays how many; 0 means our built-in estimate).upgradableandupgrade_eta: whether the view can be upgraded now, and how long that takes.
The answer never renders, queues or charges anything, so it is safe to call before every page you build, or for every vehicle in a catalog.
Upgrade a view to high quality
/api/v1/images/upgrades| Parameter | Type | Description |
|---|---|---|
vehicle | string | A vehicle id, or make, model and year. |
view | string | The view to upgrade (default front-3-4). Color does not matter: all colors of the view are upgraded together. |
webhook_url | string | Optional public HTTPS URL that receives one POST when the upgrade has succeeded or failed. |
webhook_secret | string | Optional, with webhook_url: signs each delivery in X-CarImage-Signature. |
curl --fail-with-body \
-X POST https://carimage.dev/api/v1/images/upgrades \
-H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: upgrade-veh_errc87t1jgata-side" \
-d '{"vehicle":"veh_errc87t1jgata","view":"side"}'{
"data": {
"id": "8a1f3c52-6d0e-4c1b-9f3a-2b7d5e9c0a14",
"object": "image_upgrade",
"status": "queued",
"vehicle": {
"id": "veh_errc87t1jgata",
"make": "toyota",
"model": "camry",
"year": 2025
},
"view": "side",
"from_quality": "standard",
"quality": "standard",
"credits_charged": 20,
"refunded": false,
"error": null,
"created_at": "2026-10-22T16:40:00.000Z",
"finished_at": null,
"estimated_seconds_remaining": 35,
"webhook": null
},
"billing": {
"credits_charged": 20,
"credits_remaining": 480,
"refunded_if_not_delivered": true
},
"request_id": "req_01j9x…"
}The answer is 202 with the upgrade and a Location header. Poll it every 10–15 seconds, or give a webhook_url and wait for the call:
curl -s -H "Authorization: Bearer $CAR_IMAGE_API_KEY" https://carimage.dev/api/v1/images/upgrades/<id>
# -> data.status: "queued" | "running" | "succeeded" | "failed", data.estimated_seconds_remainingsucceeded: the stored view is now high quality. Request the image as usual; a URL you already hold returns the new picture once caches refresh (within an hour on our CDN).failed: nothing changed and the 20 credits are back (refunded: true). Before replacing a picture, we compare the new one with the stored one automatically; if it is not at least as good, we keep the stored one.- A view that is already high quality answers
200withstatus: "not_needed"and charges nothing. Asking again for an upgrade your account already has running returns that upgrade, also without a charge. - A view that is not ready yet is a free
409withcode: "not_cached": request the image once, then upgrade it. If another account is upgrading the same view, the409saysupgrade_in_progress; its result serves you too.
Send an Idempotency-Key so a retried request can never charge twice. GET /api/v1/images/upgrades lists your recent upgrades.
Webhooks
An upgrade usually finishes while a client is still polling, so polling is enough for one image. For many upgrades at once, a webhook saves the loop: we POST the same object the status endpoint returns, as { "event": "image_upgrade.succeeded" | "image_upgrade.failed", "data": … }, with the same headers and signature as 3D model webhooks. Answer with any 2xx within 10 seconds; anything else is retried up to five times.
import { createHmac, timingSafeEqual } from "node:crypto";
// Read the raw body: the signature covers the exact bytes we sent.
export async function POST(request: Request) {
const body = await request.text();
const expected = "sha256=" + createHmac("sha256", process.env.CAR_IMAGE_WEBHOOK_SECRET).update(body).digest("hex");
const given = request.headers.get("x-carimage-signature") ?? "";
if (given.length !== expected.length || !timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
return new Response("bad signature", { status: 401 });
}
const { event, data } = JSON.parse(body);
if (event === "image_upgrade.succeeded") {
// data is the same JSON GET /api/v1/images/upgrades/{id} returns.
}
return new Response(null, { status: 204 }); // any 2xx stops the retries
}How long things take
| Parameter | Type | Description |
|---|---|---|
A view that is ready | any color, size | Under a second; most answers come straight from our CDN. |
A vehicle's first image | first_render | About 36 seconds; nine in ten within 60. |
Another view of a drawn vehicle | new_view | About 39 seconds; nine in ten within 55. |
An upgrade to high quality | upgrade_master, upgrade_view | About 35–40 seconds; nine in ten within 75. |
The live figures are in every availability answer (estimates) and in each upgrade's estimated_seconds_remaining.