Car Image API
Documentation

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)

GET/api/v1/images/availability
images:readfree
GET /api/v1/images/availability parameters
ParameterTypeDescription
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.
bash
curl --fail-with-body \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://carimage.dev/api/v1/images/availability?vehicle=veh_errc87t1jgata"
200 OK
{
  "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: standard or high; null when the view is not ready.
  • render_eta: for a view that is not ready, how long its first request takes. typical_seconds is the median and slow_seconds the time nine in ten finish within, both measured on the last 3 days of real requests (samples says how many; 0 means our built-in estimate).
  • upgradable and upgrade_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

POST/api/v1/images/upgrades
images:read20 credits, refunded if not delivered
POST /api/v1/images/upgrades body
ParameterTypeDescription
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.
bash
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"}'
202 Accepted
{
  "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:

bash
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_remaining
  • succeeded: 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 200 with status: "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 409 with code: "not_cached": request the image once, then upgrade it. If another account is upgrading the same view, the 409 says upgrade_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.

Verify the signature (Node)
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

Typical waits, measured on production traffic
ParameterTypeDescription
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.