Car Image API
Documentation

3D models

POST /api/v1/3d — GLB, USDZ and FBX of any vehicle in any paint; status, webhooks, downloads.

POST /api/v1/3d turns any catalog vehicle, in any paint, into a textured 3D model: GLB, USDZ, FBX and a thumbnail. Models are built from the same renders the image API serves, so they match the pictures you already show, and every color of a vehicle is one mesh with a different texture. One request costs 1,000 credits ($1.00), charged at creation whether the model is cached, in progress or new; polling, downloads and webhooks are free.

Two models, one mesh

A 2025 Toyota Camry, made by the endpoint below in silver and then retextured to red: wheels, glass and trim untouched, only the panels repainted. Drag to orbit. Each file is about 1 MB here because it is meshopt-compressed with WebP textures for the page; the API delivers the full-resolution originals listed under Files.

Interactive 3D model of a 2025 Toyota Camry made by the Car Image API

Same mesh, retextured per color.

Endpoints

POST/api/v1/3d
images:read1,000 credits
GET/api/v1/3d/{id}
images:readfree
GET/api/v1/3d/{id}/files/{kind}
images:readfree
GET/api/v1/3d?limit=
images:readfree

The same four operations are the create_3d_model and get_3d_model MCP tools, the car-image 3d create|get|download|list CLI commands and the SDK's create3dModel, get3dModel, list3dModels and download3dModel.

Create a model

POST/api/v1/3d
images:read1,000 credits
POST /api/v1/3d body
ParameterTypeDescription
makerequired
string
Make name or slug, as on GET /api/v1/images/car. Omit when you send vehicle.
modelrequired
string
Model name or slug. Omit when you send vehicle.
yearrequired
integer
Model year. Omit when you send vehicle.
vehicle
string
A stable vehicle id (veh_…) in place of make, model and year. Sending both is a 400; an unknown id is a 404.
color
preset | hex
One of the 15 preset names, or any hex ("#1a2b3c" or "1a2b3c"; a hex equal to a preset swatch is that preset). Echoed as the preset name or #1a2b3c. Every color of a vehicle shares one mesh, so a second color is a retexture, not a rebuild.Default: silver
webhook_url
string (https)
A public HTTPS URL that receives one POST when the model is ready or failed. See Webhooks.
webhook_secret
string
Signs the webhook body (HMAC-SHA256). Only with webhook_url; never echoed.
HeaderExampleMeaning
Idempotency-Key3d-toyota-camry-2025-redOptional but recommended: the same key and body within 24 hours replays the first response instead of charging again, exactly as on POST /api/v1/image-urls.
bash
curl --fail-with-body \
  -X POST https://carimage.dev/api/v1/3d \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3d-toyota-camry-2025-red" \
  -d '{"make":"toyota","model":"camry","year":2025,"color":"red"}'
202 Accepted (200 when the model is already ready)
{
  "data": {
    "id": "0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10",
    "object": "3d_model",
    "status": "processing",
    "progress": 35,
    "stage": "geometry",
    "vehicle": {
      "id": "veh_errc87t1jgata",
      "make": "toyota",
      "model": "camry",
      "year": 2025
    },
    "color": "red",
    "generator": "meshy-7",
    "files": null,
    "polycount": null,
    "error": null,
    "estimated_seconds_remaining": 170,
    "webhook": null,
    "created_at": "2026-09-16T09:12:04.000Z",
    "updated_at": "2026-09-16T09:12:40.000Z",
    "ready_at": null
  },
  "billing": {
    "charged_on": "creation",
    "credits_charged": 1000,
    "credits_remaining": 3870
  },
  "request_id": "req_01j9x…"
}

id is your request, scoped to your account; the shared model behind it is never exposed. When another customer, or you, already asked for this vehicle in this color, the answer is 200 with status: "ready" and the files straight away. A Location header points at /api/v1/3d/{id} either way.

Status lifecycle

statusMeaningThen
queuedAccepted and charged; the source views are being rendered or the provider has not picked the task up yet.processing
processingThe mesh is being built (stage geometry) or painted (stage texture). progress climbs from 0 to 100.ready or failed
readyfiles holds every format; ready_at is set; the webhook, if any, has been queued.final
failederror carries a code and a message; the credits were refunded. Ask again and a fresh attempt is made.final

The first model of a vehicle takes 3–5 minutes: the four orthogonal views are rendered (stage views), the mesh is built from them (geometry) and textured (texture). Every further color of that vehicle reuses the mesh and only repaints it, about 1–2 minutes. progress is 0–100 and estimated_seconds_remaining is a rough guide for a progress bar, not a promise; it is null once the model is ready or failed.

Poll or list

GET/api/v1/3d/{id}
images:readfree

Returns the same data object, refreshed. While the model is being made, polling every 10–15 seconds is plenty: the server re-reads the generator when its last look is older than 15 seconds, so a tighter loop only sees the same answer. Once status is ready, files carries a URL, byte count and content type per format.

200 OK
{
  "data": {
    "id": "0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10",
    "object": "3d_model",
    "status": "ready",
    "progress": 100,
    "stage": "done",
    "vehicle": {
      "id": "veh_errc87t1jgata",
      "make": "toyota",
      "model": "camry",
      "year": 2025
    },
    "color": "red",
    "generator": "meshy-7",
    "files": {
      "glb": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/glb",
        "bytes": 6412309,
        "content_type": "model/gltf-binary"
      },
      "usdz": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/usdz",
        "bytes": 7204118,
        "content_type": "model/vnd.usdz+zip"
      },
      "fbx": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/fbx",
        "bytes": 6530772,
        "content_type": "application/octet-stream"
      },
      "thumbnail": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/thumbnail",
        "bytes": 214907,
        "content_type": "image/png"
      }
    },
    "polycount": 59842,
    "error": null,
    "estimated_seconds_remaining": null,
    "webhook": null,
    "created_at": "2026-09-16T09:12:04.000Z",
    "updated_at": "2026-09-16T09:13:21.000Z",
    "ready_at": "2026-09-16T09:13:21.000Z"
  },
  "request_id": "req_01j9x…"
}
GET/api/v1/3d?limit=
images:readfree

Your recent requests, newest first, as data: [ … ] of the same objects. limit is 1–50 (default 20); the response also carries pricing.credits_per_model, the current price of a request.

Files

GET/api/v1/3d/{id}/files/{kind}
images:readfree
kindContent-TypeTypical sizeUse
glbmodel/gltf-binaryabout 6 MBWeb viewers (<model-viewer>, three.js), Blender, Unity, Unreal, game engines.
usdzmodel/vnd.usdz+zipabout 7 MBiOS AR Quick Look and visionOS; ios-src on <model-viewer>.
fbxapplication/octet-streamsimilar to the GLBMaya, 3ds Max and pipelines that still want FBX.
thumbnailimage/pnga few hundred KBA transparent still of the model for lists and posters.

Each file URL answers 302 with a signed URL on our storage that is valid for one hour and needs no key; curl -L and every download tool follow it. Fetch with your API key from a server, then either store the file with your other assets or hand the signed Location to a browser for the hour. Before the model is ready the URL answers 409 with Retry-After: 15; a kind outside glb, usdz, fbx, thumbnail is a 404.

bash
curl -s -H "Authorization: Bearer $CAR_IMAGE_API_KEY" https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10
#  -> data.status: "ready", data.files.glb.url, data.files.usdz.url, …

curl -L --fail-with-body \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/glb \
  --output camry-2025-red.glb

Webhooks

Pass webhook_url and the endpoint POSTs once when the model settles, so nothing has to poll. The body is the same JSON GET /api/v1/3d/{id} returns, wrapped in an event:

POST <webhook_url>
{
  "event": "3d_model.ready",
  "data": {
    "id": "0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10",
    "object": "3d_model",
    "status": "ready",
    "progress": 100,
    "stage": "done",
    "vehicle": {
      "id": "veh_errc87t1jgata",
      "make": "toyota",
      "model": "camry",
      "year": 2025
    },
    "color": "red",
    "generator": "meshy-7",
    "files": {
      "glb": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/glb",
        "bytes": 6412309,
        "content_type": "model/gltf-binary"
      },
      "usdz": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/usdz",
        "bytes": 7204118,
        "content_type": "model/vnd.usdz+zip"
      },
      "fbx": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/fbx",
        "bytes": 6530772,
        "content_type": "application/octet-stream"
      },
      "thumbnail": {
        "url": "https://carimage.dev/api/v1/3d/0c5e0b2a-7f4a-4c8e-9d3b-2f1e6a9c4d10/files/thumbnail",
        "bytes": 214907,
        "content_type": "image/png"
      }
    },
    "polycount": 59842,
    "error": null,
    "estimated_seconds_remaining": null,
    "webhook": null,
    "created_at": "2026-09-16T09:12:04.000Z",
    "updated_at": "2026-09-16T09:13:21.000Z",
    "ready_at": "2026-09-16T09:13:21.000Z"
  },
  "sent_at": "2026-09-16T09:13:22.000Z"
}
HeaderExampleMeaning
X-CarImage-Event3d_model.ready | 3d_model.failedThe event, also in the body.
X-CarImage-Delivery5c1d…Unique per delivery attempt; use it to ignore a duplicate.
X-CarImage-Signaturesha256=9f2a…Only with webhook_secret: hex HMAC-SHA256 of the raw request body, keyed with the secret you sent.

Answer with any 2xx within 10 seconds. A non-2xx or a timeout is retried with backoff up to five times, each with a new X-CarImage-Delivery. The URL must be HTTPS on a public host (no loopback, private ranges or internal names); an invalid one is a 400 before anything is charged. Verify the signature over the raw bytes before parsing:

app/api/car-image-webhook/route.ts
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 === "3d_model.ready") {
    // data is the same JSON GET /api/v1/3d/{id} returns: data.files.glb.url etc.
  }
  return new Response(null, { status: 204 }); // any 2xx stops the retries
}

Download and embed

Download the GLB (and the USDZ for iOS) with your key, host them next to your other assets and show them with Google's <model-viewer>, the same element the demo above uses. The thumbnail makes a good poster while the model loads.

html
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.1.0/model-viewer.min.js"></script>

<model-viewer
  src="/models/camry-2025-red.glb"
  ios-src="/models/camry-2025-red.usdz"
  poster="/models/camry-2025-red.png"
  alt="2025 Toyota Camry, red"
  camera-controls auto-rotate ar
  shadow-intensity="1"
  style="width: 100%; aspect-ratio: 1"
></model-viewer>

Pricing

  • 1,000 credits ($1.00) per request, charged at creation: cached, in progress or new, the price is the same, exactly as with images. An Idempotency-Key makes a retried request replay instead of charging twice.
  • Polling, listing, downloads and webhook deliveries are free.
  • A model that fails is refunded in full; ask again and a fresh attempt is made.
  • 402 when the balance is short of 1,000 credits (with balance and required_credits); nothing is charged.
  • 503 with retry_after_seconds (and Retry-After) when 3D generation is at its daily capacity or switched off; nothing is charged, images are unaffected, and the request succeeds again once the window resets.

Errors

  • 400 — a bad body: unknown field, vehicle together with make, model and year, an unparseable color, a webhook_url that is not public HTTPS, or a webhook_secret without a URL.
  • 402 — fewer than 1,000 credits. Do not retry; top up.
  • 404 — the vehicle is not in the catalog, no vehicle has that id, or no request of yours has that model id.
  • 409 — a file was asked for before the model was ready (Retry-After), or an Idempotency-Key retry overtook the first request.
  • 503 — at capacity or disabled; wait retry_after_seconds.

Every failure is application/problem+json. See Errors.