Car Image API
Meter v1.15.5
Vehicle images in your chat
Competitor profile
Developer declarations; not independently tested. Listing last checked Nov 4, 2026. Package observed Nov 3, 2026.
Declared capabilities
- Read
- Write
Not specified in the available declarations: limitations, pricing & account requirements.
Onboarding example
Show me a random car image.
Description
From the marketplace listing
Show a vehicle directly in your conversation or add it to a page, email or document. Find a make, model and year in the catalog, then render it on a transparent background in eight camera views and any paint color. Signed image URLs load without an API key in the browser. Decode VINs, fetch catalog make logos, or create textured 3D models in GLB, USDZ and FBX formats.
Language: English · Automatically detected from descriptions.
Changes
No changes recorded yet. Follow this plugin to keep track of future changes.
First seen Oct 29, 2026. Last seen Nov 4, 2026.
Similar plugins
Find competitors to follow as you develop your plugin.
-
Create and edit images or generate videos in chat, organize creations in Board, and use Canvas for visual projects.
Shared terms: image, chat, browser, model
-
Signed-in OneInfer models, media, Studio, and GPU machines.
Shared terms: chat, api, key, signed
-
Create jewelry and product images with Lustra Studio, use specialist image tools, and retrieve saved project images.
Shared terms: image, color, studio, model
-
Generate engine-ready 3D assets from a text prompt or a reference image and land them directly in your project, with texturing, rigging, ...
Shared terms: image, api, format, quality
-
Gulab connects your Gulab account to a range of image and video generation models. Describe an image or a short clip and Gulab submits th...
Shared terms: image, background, model, project
Keywords
Files & skills
File archives
Skill instructions
car-3d15.4 KB
---
name: car-3d
description: Get a textured 3D model (GLB, USDZ, FBX and a thumbnail) of any real vehicle in any paint color from the Car Image API, for AR views, configurators, game assets, product pages and 3D scenes, and put it on a web page with a two-line embed that Car Image hosts. Covers the order of work (look the vehicle up, confirm the price, create by vehicle id), what a model costs (100 credits, charged at creation, free once the account owns that vehicle in that color), how long it takes and how to wait in a conversation, polling versus webhooks, verifying the webhook signature, downloading the files, publishing a model and the <car-3d> element. Use when the user wants a 3D model, a GLB, a USDZ, an FBX, an AR-ready asset of a car, or a 3D car on a page; do not use for 2D renders (car-image), signed image URLs (car-image-urls) or finding which vehicle to model (vehicle-catalog).
---
# 3D models of real vehicles
`POST /api/v1/3d` turns any catalog vehicle, in any paint, into a textured 3D model: **GLB, USDZ, FBX, a thumbnail and `glb_web`**, a smaller browser build of the GLB. Models are built from the same renders the image API serves, so a model matches the pictures the user already shows, and every color of a vehicle is one mesh with a different texture.
With MCP connected the tools are `create_3d_model`, `get_3d_model` and `publish_3d_model`; they are on `?toolset=all`, which the plugin configures, and a connection on the bare URL has the core seven only (the `car-image-mcp` skill). Over REST: `POST /api/v1/3d`, `GET /api/v1/3d/{id}`, `GET /api/v1/3d/{id}/files/{kind}`, `GET /api/v1/3d?limit=`, `POST|DELETE /api/v1/3d/{id}/publish`, and the key-free `GET /api/v1/3d/public/{public_id}`. The CLI has `car-image 3d create|get|download|list|publish`; the SDK has `create3dModel`, `get3dModel`, `list3dModels`, `download3dModel`, `publish3dModel` and `unpublish3dModel`. Full reference: [`/docs/3d`](https://carimage.dev/docs/3d?ref=plugin).
## The order of work
1. **Look the vehicle up** (free; the `vehicle-catalog` skill): `resolve_vehicle` for a name, `decode_vin` for a VIN. Keep the vehicle id. A model of the wrong car costs a hundred times an image of it, so this step is never skipped, and a lookup that is not `high` confidence in the year asked for goes back to the user first.
2. **Say what it is and what it costs, and wait for a yes**: the year, make and model the lookup named, the paint, 100 credits ($1.00) unless the account already owns it, and ten to twenty minutes for a first model. A preview image of that vehicle in that paint (1 credit; the `car-image` skill shows one in the reply) is a cheap way to agree on both before the larger charge.
3. **Create it by id**: `create_3d_model({ vehicle: "veh_…", color, publish: true })`. Keep the request `id`.
4. **Wait without hammering**, then hand over the files or the embed.
## What it costs — say this first
- **100 credits ($1.00) per vehicle and color**, charged at creation whether the model is cached, in progress or new. Cache state never changes the price, exactly as with images.
- **Free once owned.** An account that already holds a live request for a vehicle in a color owns it: ordering it again creates a new request for nothing (`billing.credits_charged: 0`, `billing.already_owned: true`), whatever spelling, whether the model is ready or still being made, and even after Car Image rebuilds models with a better recipe. So a re-order is never a second bill; only a failed model (refunded, not owned) makes the next attempt a charged one.
- Polling, listing, downloads, webhook deliveries, **publishing and every load of a published model are free**.
- A model that **fails is refunded** in full. A `503` at capacity charges nothing.
- A request is a hundred times an image: **tell the user the price and confirm before calling `create_3d_model`** for a vehicle and color the account does not own yet, and call `get_account` first when the balance may be short. Never retry a `402`.
## How long it takes
| Case | Wall time | Why |
| --- | --- | --- |
| First model of a vehicle | **10–20 minutes** | The four source views are rendered and waited for (`stage: views`), the mesh is built and finished (`geometry`) and the color composed (`texture`). |
| Another color of the same vehicle | **1–2 minutes** | The finished mesh already exists; only the paint changes. |
| Someone already asked for this vehicle in this color | immediate | `200` with `status: "ready"` and the files; charged unless the account already owns it. |
Say this before the user waits. Do not present a model as instant.
## The request
```json
{ "make": "Toyota", "model": "Camry", "year": 2025, "color": "red", "publish": true }
```
- Name the vehicle with `make`, `model` and `year`, **or** with `vehicle`, a stable catalog id such as `veh_errc87t1jgata` (from `search_vehicles`, `resolve_vehicle` or `decode_vin`). Both together is a `400`; an unknown id is a `404`.
- `color` is one of the 15 presets or any hex (`"#1a2b3c"` or `"1a2b3c"`); the response echoes `#1a2b3c`. Default `silver`.
- `publish: true` publishes the model at once (below). Free. Pass it whenever the model is for a web page.
- `webhook_url` (public HTTPS) and `webhook_secret` are optional; see below.
- Send an `Idempotency-Key` header over REST so a retried request replays the first answer instead of charging again (the SDK and CLI do this for you).
Prefer `vehicle`: the id comes from the lookup, so the model is of the car the user confirmed. A name typed from memory can answer `404`, or resolve to a neighbouring model, and a model of the wrong car costs 100 credits.
## The response and its lifecycle
`202 Accepted` while the model is queued or processing, `200` when it was already ready:
```json
{
"data": {
"id": "0c5e0b2a-…", "object": "3d_model",
"status": "processing", "progress": 35, "stage": "geometry",
"vehicle": { "id": "veh_errc87t1jgata", "make": "toyota", "model": "camry", "year": 2025 },
"color": "red", "generator": "meshy-7.1",
"files": null, "polycount": null, "error": null,
"estimated_seconds_remaining": 170, "webhook": null,
"public": { "id": "m3d_7f2k9q4hxn2b5c8d", "url": "https://carimage.dev/api/v1/3d/public/m3d_7f2k9q4hxn2b5c8d", "files": null, "embed": { "script": "https://carimage.dev/embed/3d.js", "html": "<script src=\"https://carimage.dev/embed/3d.js\" async></script>\n<car-3d model=\"m3d_7f2k9q4hxn2b5c8d\"></car-3d>" }, "published_at": "…" },
"created_at": "…", "updated_at": "…", "ready_at": null
},
"billing": { "charged_on": "creation", "credits_charged": 100, "credits_remaining": 3870, "already_owned": false },
"request_id": "…"
}
```
`status` goes `queued` → `processing` → `ready` or `failed`. `progress` is 0–100; `estimated_seconds_remaining` is a rough guide, not a promise. When `ready`, `files` holds `glb`, `glb_web`, `usdz`, `fbx` and `thumbnail`, each `{url, bytes, content_type}`. When `failed`, `error` carries `{code, message}` and the credits were refunded; asking again makes a fresh attempt. `public` is `null` until the request is published.
`id` is the user's request; the shared model behind it is never exposed. Every request is its own record with its own webhook and its own publish state.
## Waiting: poll or webhook
**Poll** `get_3d_model` / `GET /api/v1/3d/{id}` every **10–15 seconds**. The server re-reads the generator only when its last look is older than 15 seconds, so a tighter loop sees the same answer and wastes the rate limit. Stop on `ready` or `failed`. In a script, `car-image 3d get <id> --wait` and `car-image 3d create … --wait` do the waiting.
**Webhook**: pass `webhook_url` and the API POSTs once when the model settles:
- Body: `{"event": "3d_model.ready" | "3d_model.failed", "data": <the same JSON as GET>, "sent_at": "…"}`.
- Headers: `X-CarImage-Event`, `X-CarImage-Delivery` (unique per attempt; ignore a duplicate) and, when `webhook_secret` was given, `X-CarImage-Signature: sha256=<hex HMAC-SHA256 of the raw body keyed with the secret>`.
- The endpoint must answer any `2xx` within 10 seconds; a non-2xx or a timeout is retried with backoff up to five times.
Verify the signature over the **raw bytes**, with a constant-time compare, before parsing:
```ts
import { createHmac, timingSafeEqual } from "node:crypto";
/** `secret` is the webhook_secret the model was requested with, from the app's own secret settings. */
export function signatureIsValid(rawBody: Buffer, given: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
// In the handler, before parsing the body:
if (!signatureIsValid(rawBody, request.headers.get("x-carimage-signature") ?? "", secret)) {
return new Response("bad signature", { status: 401 });
}
```
Use a webhook when the user's app has a public HTTPS endpoint; poll otherwise. Never send a `webhook_secret` anywhere but the request body.
## In a conversation
A chat has no page to embed into and no terminal to download with, so plan for what the user can open.
- **Publish at creation** (`publish: true`). The `files` URLs need the account's credential on the first hop; the `public.files` URLs do not, so they are the links a person can click: `glb` to download or drop into any 3D viewer, `usdz` to open in AR on an iPhone or iPad, `poster` for a still. Say that anyone with a public link can load the model, and offer to unpublish when they are done.
- **Do not hold the conversation for the wait.** After creating, say how long it should take (`estimated_seconds_remaining`), check `get_3d_model` once or twice at most while you have something else to do, and otherwise tell the user to ask for the model again later: the request id is all you need, and the wait costs nothing.
- **When it is ready**, give the links with their sizes (`bytes`), say which to use for what, and show the still.
- **When it fails**, the credits came back. Say so, and offer another attempt rather than starting one.
## On a web page: publish it
When the model is for a page — a listing, a product page, a configurator, a demo — do not download and host files. **Publish** it, with `publish: true` at creation or `publish_3d_model` (`POST /api/v1/3d/{id}/publish`, `car-image 3d publish <id>`, `client.publish3dModel(id)`) at any time, and paste `public.embed.html`:
```html
<script src="https://carimage.dev/embed/3d.js" async></script>
<car-3d model="m3d_7f2k9q4hxn2b5c8d" view="front-3-4" spin backdrop="studio" ar></car-3d>
```
- The script goes on the page once; each `<car-3d>` shows one model. **No API key anywhere near the browser** — the element reads the model's public JSON and Car Image serves the files from its CDN. Free, every load.
- It shows the poster at once, loads Google's `<model-viewer>` the first time it scrolls into view, and, if the model is still being made, shows a progress note and updates itself when it is ready — so you can publish at creation and ship the page before the model exists.
- Attributes: `view` (`front-3-4` default, `front`, `side`, `rear-3-4`, `rear`, `rear-3-4-right`, `side-right`, `front-3-4-right`, `top`: the image views' eight angles plus a top view), `spin` (or a number of degrees per second), `backdrop` (`none` default, `light`, `dark`, `studio`, or any CSS background: `#0f1117`, a gradient, `url(…)`), `ar` (an AR button on phones), `static` (no controls), `no-zoom`, `alt`. Anything else (`exposure`, `camera-orbit`, `shadow-intensity`, …) is passed to `<model-viewer>`. Size it with CSS: it is `display: block`, 100% wide, 4:3 by default.
- Pages that already load `<model-viewer>` can skip the script and use `public.files` directly: `model.glb` (the browser build), `model.usdz` (for `ios-src`) and `poster.png` are stable URLs that redirect to immutable copies on the CDN, readable from any origin. `GET /api/v1/3d/public/{public_id}` (no key) returns the same JSON the element reads, `embed.model_viewer` included.
- `public.id` is unguessable, not secret: anyone holding the link can load the model. Unpublish (`unpublish: true`, `DELETE`, `--unpublish`) when it should not be: the links stop working within the hour, and a later publish gets a new id.
## Downloading the files
For an app, a game engine, Blender or a pipeline, download instead. `GET /api/v1/3d/{id}/files/{glb|glb_web|usdz|fbx|thumbnail}` needs the API key and 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; `car-image 3d download <id> --format glb --out car.glb` and `client.download3dModel(id, "glb")` do the same. Before the model is ready it answers `409` with `Retry-After`.
| File | Content type | Typical size | Use |
| --- | --- | --- | --- |
| `glb` | `model/gltf-binary` | about 100 MB | The original, unremeshed (about three million triangles, 4k textures): three.js, Blender, Unity, Unreal, further processing |
| `glb_web` | `model/gltf-binary` | about 30 MB | The browser build (meshopt, WebP textures): any web viewer, when you host it yourself |
| `usdz` | `model/vnd.usdz+zip` | about 25 MB | iOS AR Quick Look, visionOS; converted from a decimated build (about 300,000 triangles, the same 4k textures) |
| `fbx` | `application/octet-stream` | similar to the GLB | Maya, 3ds Max, older pipelines |
| `thumbnail` | `image/png` | a few hundred KB | A transparent still for posters and lists |
Never put the API key, or the hour-long signed URL, in a page. Publishing exists so you never have to.
## When something fails
| Status | Meaning | Do |
| --- | --- | --- |
| 400 | Bad body: `vehicle` with make/model/year, an unparseable `color`, a `publish` that is not a boolean, a `webhook_url` that is not public HTTPS, a `webhook_secret` without a URL. | Fix and retry once. Nothing was charged. |
| 402 | Fewer than 100 credits. | **Stop and ask the human** to top up. Never buy credits on your own. |
| 404 | Vehicle not in the catalog under that name, unknown vehicle id, a request id that is not the user's, or an unpublished public id. Nothing was charged. | Look the vehicle up (`resolve_vehicle`; the problem's `suggestions` carry ids) and create by `vehicle`; check the id. |
| 409 | A file was asked for before `ready`, a failed model was asked to publish, or an `Idempotency-Key` retry overtook the first request. | Wait `Retry-After`, then poll `get_3d_model`. |
| 503 | 3D generation is at its daily capacity (`code: model_3d_at_capacity`, `retry_after_seconds`) or switched off. Nothing charged; images are unaffected. | Tell the user when it resumes; do not loop. |
Every failure is `application/problem+json` with a `request_id`; keep it. The full table is in the `car-image` skill's [references/errors.md](../car-image/references/errors.md).
## Never do these on your own
- Create a model of a vehicle you have not looked up, or one the user has not confirmed by name.
- Create a model the user did not ask for, or a batch of them. Confirm the count and the cost (100 credits each, unless the account already owns that vehicle in that color) first.
- Retry a `402`, or poll faster than every 10 seconds.
- Put the API key, a `webhook_secret` or a signed file URL in a page, a repository or a log. Publish instead.
- Publish a model the user wanted kept private, or leave one published after they asked for it to be taken down.
- Claim the model reproduces a specific trim, options package or an individual listed vehicle: it is built from catalog-bounded studio renders, not a scan of a real car.
Referenced files: 1 — file references are metadata, not package contents.
car-image29.2 KB
---
name: car-image
description: "Show or fetch a studio-quality, transparent-background image of a real vehicle (any make, model and year from 1990-2027), in a conversation or for a product page, a listing, a comparison, an email or a deck, and fetch catalog make logos with get_make_logo. Every image takes two steps - look the vehicle up first (resolve_vehicle, search_vehicles or decode_vin, free, the vehicle-catalog skill), then show it by its vehicle id (create_car_image_urls, 1 credit; clickable links and browser viewing in ChatGPT/Codex, inline Markdown in Claude). Covers the Car Image API by Meter - the chat workflow, angles, paint, sizing, the per-image credit cost, and how to handle 402, 404 and 429. Works before sign-in: lookups and a free preview of the picture a vehicle's page already shows (preview_car_image), then the account for the exact image. Use for any request involving a picture of a car, truck, motorcycle or other vehicle, including \"show me a car\" or \"a random car\"; do not use for embedding URLs in a browser or document (car-image-urls), SDK and CLI code (car-image-sdk), 3D models (car-3d), or connection setup (car-image-mcp)."
---
# Car Image API
Studio-quality, transparent-background renders of any vehicle in the open [`@meterapp/vehicle-db`](https://github.com/MeterApp/vehicle-db) catalog — 1,600 makes, 41,975 models, model years 1990-2027. Eight camera views, any paint color, PNG/WebP/JPG up to 1024 px.
## Two steps, every time
**1. Look the vehicle up (free).** Do this before every render, including for a car you know well and a car you picked yourself. If the `vehicle-catalog` skill is available, use it for this step: it covers ambiguity, VINs, years and what to do when nothing matches. The short version:
- `resolve_vehicle({ query })` with the year, make and model, plus the view and paint if the user named them: `"red 2018 Mazda MX-5 Miata, side view"`. It returns `params.vehicle_id` with a `view` and a `color`, `confidence` and up to five `candidates`. `extracted` is what the phrase itself said: where `extracted.view` or `extracted.color` is null, the value in `params` is only the default (`front-3-4`, `silver`), not something the user chose. Leave descriptions out of the phrase ("the electric one", "their sporty model"): it reads names, not descriptions. When the user gave no year, put one in and say which you chose.
- `decode_vin({ vin, year? })` when the user gave a VIN; `search_vehicles({ query, year?, limit? })` to list a make's models or a model's years.
**2. Show it by id (1 credit).** `create_car_image_urls` with one `images` entry containing `vehicle: "veh_…"`, `view` and `color`. For a chat preview use `ttl_seconds: 604800` (the URL serves for seven days) and `max_uses: 0` (no limit on loads), and leave `renew` out unless the user asks for a link that outlives the week. Never retype the make, model and year once you hold the id. `view` defaults to `front-3-4` and `color` to `silver`: when the user named no paint, pick one that suits the car or leave the default, and say which it is.
```
resolve_vehicle({ query: "red 2018 Mazda MX-5 Miata, side view" })
→ params: { vehicle_id: "veh_59854qbgfvar3", make: "mazda", model: "mx-5", year: 2018, view: "side", color: "red" }, confidence: "high"
create_car_image_urls({ images: [{ vehicle: "veh_59854qbgfvar3", view: "side", color: "red" }], ttl_seconds: 604800, max_uses: 0 })
→ data[0].markdown, data[0].url, data[0].expires_at; billing.credits_charged: 1
```
**Why the lookup is not optional.** The catalog files cars under its own names, which are often not the names people, or you, use. That Miata is `mx-5`; a Mercedes C 300 is `c-300` in one year and `c-class` in another; a name the catalog used in the 1990s may be gone by 2018. A make and model typed from memory can answer `404` for a car the catalog carries, and a name that does resolve can land on a neighbouring model. The lookup costs nothing and takes one call; a wrong render costs a credit and the user's trust.
**Read the lookup before you spend:**
| The lookup says | Do |
| --- | --- |
| `params.year` is not the year asked for (`extracted.year`) | Do not render it. The car may be filed under another name that year: `search_vehicles` with the make, the model family and `year`. If that finds it, render that id and say which catalog model it is. If not, the catalog lacks that model year: say so, offer the year it has, and ask. |
| `confidence: "low"` | Show the candidates and ask. Do not render. |
| `confidence: "medium"`, in the year asked for | Render, and say in one line which vehicle you chose. |
| `confidence: "high"`, in the year asked for | Render. |
| No match | Not yet a missing vehicle: send only the year, make and model again, or `search_vehicles` with the model family alone. When the catalog really lacks it, say the catalog does not carry it and stop; never substitute another car. |
The rows are in order: the first one that fits decides. `candidates` lists the nameplate's other variants that year (a Giulia Quadrifoglio next to the Giulia); offer them when the user may have meant one, and never treat them as a reason to stall a `high` match.
**Skip the lookup only** when you already hold the vehicle's id from this conversation (another angle or color of the car you just rendered) or the user handed you a `veh_…` id.
## Before the account is connected
The plugin runs two servers. **`car-image-catalog`** needs no account and works from the first session: `resolve_vehicle`, `search_vehicles`, `preview_car_image`, `list_image_options`, `get_pricing` and `describe_api`. **`car-image`** is the account: the image in the view, paint and size asked for, signed URLs, VINs, lists, logos, 3D models and the balance. Until the user signs in, the host lists no `create_car_image_urls`; Claude Code shows `plugin:car-image:car-image` as needing authentication and may list an `authenticate` tool for it (`mcp__plugin_car-image_car-image__authenticate`).
When the user asks for a picture and `create_car_image_urls` is not available:
1. **Look the vehicle up anyway**, as in step 1. Nothing about the lookup changes, except that `decode_vin` is on the account server: before sign-in, ask for the year, make and model a VIN stands for, or connect first.
2. **Show the free preview.** `preview_car_image({ vehicle })`, with `view` and `color` only when the user named them, returns the picture the vehicle's page on carimage.dev already shows, when one is stored. Display `data.preview` by the host rules below (its `markdown` in Claude's apps; in a terminal, the plain `url` on its own line) and say it is a preview, naming its view and paint when they differ from what was asked. A preview URL never expires. It never renders: a `null` preview means nothing is stored for that car yet. `data.stored` lists every stored view and paint, and `data.page` is the vehicle's page.
3. **Offer the account for what the preview cannot do, in one or two lines:** another view, paint or size, a file, or the image in a page, app or document (an image from the account costs 1 credit and carries the plan's license; the preview is for looking at). A preview in the view and paint asked for answers the request: offer the rest, and never render the same picture again for a credit. To connect, in Claude Code call the `authenticate` tool when it is listed and give the user the URL it returns: it opens Car Image's sign-in provider (a supabase.co address), then carimage.dev, and works while this session is open. Otherwise ask them to run `/mcp`, choose `plugin:car-image:car-image` and **Authenticate**. In other hosts, connect Car Image in the MCP or connector settings. Signing up is free and comes with 100 credits; the user needs no key for this.
4. **When the user says they have signed in, or the account tools appear, finish the original request** without asking them to say it again: `create_car_image_urls` with the id you already hold.
Do not stop at "authentication required", and do not send the user to the website to make the image by hand. Work that needs no image needs no account either: explaining views, sizes and prices, or writing code against the API (`describe_api`, `list_image_options`, `get_pricing`; the code itself needs the user's own key, the `car-image-sdk` skill).
## In a conversation
In ChatGPT and Codex, show each exact returned `data[].url` as a normal clickable Markdown link, for example `[View the vehicle image](https://example.com/image.png)` with the returned URL substituted. Do not use Markdown image syntax or paste the tool's image `markdown` into these chats, even if generic tool text recommends it. Open that same URL with an available browser-opening tool, preferably in the in-app browser (for example, `open_in_codex` with a browser target). Retain the clickable link in the final reply whether opening succeeds or not. If no browser tool is available, just provide the link. Do not claim the browser opened unless the tool confirms it.
In Claude, keep rendering inline: paste each returned `data[].markdown` outside code fences. If only `url` is returned, use `` with the exact returned URL. Do not automatically open a browser. Other hosts may use inline images when supported, otherwise a clickable link.
Include a short vehicle caption and expiry. Reuse unexpired URLs; do not download the image or make another paid image call to work around chat display. These host-specific rules apply to chat replies; website and document embeds still use image markup.
Reuse an unexpired URL already in the conversation for the same image. Use `get_car_image` for explicit image-byte or file requests: its MCP attachment may be available to the model without appearing in the user's reply. Do not call it before minting a chat URL, or mint again to fix display; both cost credits. If the client cannot display images, link the existing URL and explain the limitation.
- **Ask for a frame that suits a chat**, in the `images` entry beside `vehicle`: `width: 960, height: 600, trim: true, padding: 8, background: "#f4f4f4"` gives a wide image the car fills, on a light surface that reads in both light and dark themes. The dimensions and the padding are numbers and `trim` is a boolean. With no sizing at all the image is a 1024 px square with the car floating in the middle; without a `background` a dark car can vanish on a dark theme. Keep the background transparent when the image is going onto a page.
- **Default to the hero angle**, `front-3-4`, unless the user named one.
- **Say what is shown**, in one or two lines: year, make, model, view and paint, and that it is a studio render of the model. Mention `credits_remaining` when it is under 10 or the user asks.
- **Offer the next step**, once: another angle or paint, the same view of another car to compare, a link for a page or email (`car-image-urls`), a 3D model (`car-3d`).
- **When the user did not name a car** ("a random car", "surprise me", "a fun convertible"), choose one yourself: a specific year, make and model, and a paint. Look it up and render it like any other, and name your pick in the reply. Vary the pick from one request to the next.
### What people ask for
| Request | Calls | Credits |
| --- | --- | --- |
| "Show me a 2024 Porsche 911" | lookup, `create_car_image_urls` → host-specific display above | 1 |
| "Now from the side" / "in blue" | `create_car_image_urls` with the same `vehicle`, new `view` or `color` | 1 each |
| "Show me every angle" | the same `vehicle`, all eight views | 8. Look the car up, then ask before rendering anything: all eight for 8 credits, or front-3-4, side and rear-3-4 for 3 |
| "What paint suits it?" | the same `vehicle` and `view`, one call per paint | 1 per paint: agree the shortlist first |
| "Compare the RAV4 and the CR-V" | one lookup per car, then the same `view`, `color` and size for each. The tools return images, not specifications: add the comparison in words yourself and say those figures are not from the catalog | 1 per car |
| "What does my car look like?" with a VIN | `decode_vin`, then `create_car_image_urls` with `vehicle: vehicle.id` in its image; show the decoded trim and engine as text | 1 |
| "How did the Civic change?" | one lookup per model year, the same `view` and `color` | 1 per year |
| "The Toyota logo" | `get_make_logo` | 1 |
| "How many credits do I have?" / "What does this cost?" | `get_account` / `get_pricing` | free |
| "That looks wrong" / "That's great" | no call: a render is rated from the Recent images card on [the dashboard](https://carimage.dev/dashboard?ref=plugin#feedback) | none |
| "You don't have my car" | no call: say the catalog does not carry it and stop; never substitute another car | none |
Anything that renders more than three images the user did not count is a batch: say the number and the cost, and wait for a yes.
## What an image costs — say this before a big batch
- Every delivered image costs **exactly 1 credit**, whether it was generated or served from cache.
- A signed delivery URL costs **1 credit when created**. Loading it is free until it expires.
- **A plan carries the license and a monthly credit allowance.** Free: $0, **100 credits once at signup** (no card), evaluation and personal projects, up to 100 distinct vehicles a month. Pro: $29/month, 25,000 credits a month, commercial license (kept by every render it delivers after the plan ends), 2,500 distinct vehicles a month. Business: $99/month, 150,000 credits a month, 15,000 vehicles. Enterprise: custom, no cap. Every account, including Free, can buy credits at **$1 per 100 credits**; purchased credits never expire, require no subscription, have no monthly purchase cap and are spent after subscription credits, included credits reset monthly. Buying credits does not grant a commercial license; a render is licensed by the plan in force when it is delivered, so commercial work needs an active paid plan at that moment. `get_account` reports the plan in force as `data.plan`.
- Catalog search, resolve, VIN decoding, vehicle lookups by id and by list, options and the account are **free**.
- A **3D model costs 100 credits ($1.00)**, charged at creation and never again for a vehicle and color the account already owns; polling, downloads and publishing (hosting it as a key-free embed) are free. Confirm before creating one (the `car-3d` skill).
Twenty images cost 20 credits (20¢). Tell the user the number before rendering a batch they did not explicitly size, and call `get_account` first when the batch is large.
## Pick the right call
| You need | Use |
| --- | --- |
| Show a vehicle in chat | `create_car_image_urls` once → link and browser in ChatGPT/Codex; inline Markdown in Claude |
| A first look before the user has signed in | `preview_car_image` (free, catalog server; the picture the vehicle's page shows, when one is stored) → see [Before the account is connected](#before-the-account-is-connected) |
| Which vehicle a request means, and its id | `resolve_vehicle` (free) first → see the `vehicle-catalog` skill; `params.vehicle_id` is what to render |
| A VIN, full or partial | `decode_vin` (free) → year, make, model, trim, engine and the catalog `vehicle.id` to render; see the `vehicle-catalog` skill |
| To list a make's models or a model's years | `search_vehicles` (free; returns a vehicle id per year) |
| Which vehicles of a list (a catalog, a spreadsheet) the catalog carries, with their ids | `check_vehicles` (free, up to 100 per call; `car-image check <file>` or `client.checkVehicles` for longer lists) → see the `vehicle-catalog` skill |
| Image bytes or files for a script, backend or notebook | `get_car_image` (1 credit) — returns an MCP image attachment plus `credits_charged`, `credits_remaining`, `source` (`cache`/`generated`), `request_id` |
| A URL a browser, email or document can load | `create_car_image_urls` → see the `car-image-urls` skill |
| Credits remaining before a batch | `get_account` (free) |
| A catalog make logo | `get_make_logo` (1 credit) — inline image; make and transforms only; no trademark license |
| A 3D model (GLB, USDZ, FBX) of a vehicle | `create_3d_model` (100 credits; free once owned) then `get_3d_model` (free) → see the `car-3d` skill |
| A 3D model on a web page, with no key in the page | `create_3d_model` with `publish: true`, or `publish_3d_model` (free) → paste `public.embed.html`; see the `car-3d` skill |
| Valid views, colors, sizes, formats, pricing | `list_image_options` (free) |
| Current prices or product help | `get_pricing`, `search_help` (free) → see the `car-image-support` skill |
| Human-requested checkout or billing settings | `create_checkout_link`, `get_billing_link` (free links; the human confirms in the browser) → see the `car-image-support` skill |
| API reference | `describe_api` (free) |
The rows from `get_make_logo` down exist only when the server was connected with `?toolset=all` (the plugin does this) or `car-image mcp --toolset all`; a bare `https://carimage.dev/api/mcp` exposes the seven core tools above them. The `car-image-mcp` skill explains both.
Without MCP tools connected, the same operations are REST endpoints — `POST /api/v1/images/resolve`, `GET /api/v1/vehicles`, `GET /api/v1/vehicles/{id}`, `POST /api/v1/vehicles/check`, `GET /api/v1/vin/{vin}`, `GET /api/v1/images/car`, `GET /api/v1/images/logo`, `POST /api/v1/image-urls`, `POST /api/v1/3d`, `GET /api/v1/3d/{id}`, `GET /api/v1/3d/{id}/files/{kind}`, `POST|DELETE /api/v1/3d/{id}/publish`, `GET /api/v1/3d/public/{public_id}` (no key), `GET /api/v1/images/options` and `GET /api/v1/account`. The `car-image-sdk` skill covers calling them from code; the CLI equivalents are `car-image resolve …`, `car-image check …`, `car-image vin …`, `car-image get …`, `car-image logo …` and `car-image 3d …`. Base URL `https://carimage.dev`; docs at [`/docs`](https://carimage.dev/docs?ref=plugin), [`/agents.md`](https://carimage.dev/agents.md) and [`/openapi.json`](https://carimage.dev/openapi.json).
## What you can ask for
- **Views:** eight, in order around the car: `front`, `front-3-4` (the hero, the default), `side`, `rear-3-4`, `rear`, `rear-3-4-right`, `side-right`, `front-3-4-right`. `front-3-4`, `side` and `rear-3-4` show the car's left side with the nose pointing left; each `-right` twin shows its right side with the nose pointing right, so pick the one that faces into your layout
- **Colors:** any paint. The 15 presets `white black gray silver blue red green brown beige tan orange yellow gold burgundy purple` (`grey` is `gray`), any other CSS color name (`navy` is `#000080`), or any hex (`"#1a2b3c"` in JSON, `color=1a2b3c` in a URL); a custom paint costs the same 1 credit as a preset. `resolve_vehicle` reads paint words such as navy, charcoal and cream onto the presets
- **Vehicle ids:** every make, model and year has a stable id such as `veh_395yw8tn73ff8`; pass it as `vehicle` in place of make, model and year (both together is a 400)
- **Formats:** `png` (transparent), `webp`, `jpg`, or `auto` (WebP or PNG negotiated from `Accept`), up to 1024 px
- **Sizing:** `size=thumb|small|medium|large` (256/512/768/1024), or `width`/`height` up to 1024 (a larger size is clamped, a box keeping its shape) — both together return exactly that box, placed by `fit=contain|cover|inside`; `trim` crops to the car first; `background` flattens onto a solid color
### Getting the size right
`get_car_image` and each `create_car_image_urls` entry take the same sizing inputs as the REST endpoints. The source render is square; nothing is upscaled past 1024 px.
| Input | Values | Use it when |
| --- | --- | --- |
| `size` | `thumb` 256, `small` 512, `medium` 768, `large` 1024 | A square is fine. |
| `width`, `height` | up to 1024 px each (a larger box is scaled down whole) | The layout has a box. One dimension keeps the aspect ratio; both together return exactly `width`×`height` (600×400 is 600×400, no longer a 400×400 square). |
| `fit` | `contain` (default), `cover`, `inside` | Only matters with both dimensions. `contain` keeps the whole car and pads with transparency or the `background`; `cover` fills the box and centre-crops; `inside` keeps the car within the box and may return a smaller image (the old behaviour). |
| `trim`, `padding` | `true`; 0–50 | The slot is not square. `trim` crops to the car's alpha bounds before sizing so it fills the box instead of floating in the square source frame; `padding` keeps a margin, as a percentage of the car's longer side, and only applies with `trim`. |
| `background` | `transparent` (default), `white`, `black`, hex as `rrggbb`, `#rrggbb`, `rgb` or `#rgb` | The surface cannot show transparency or wants a flat color. A solid background flattens PNG and WebP too; `jpg` cannot be transparent and defaults to white. |
| `format` | `png` (default), `webp`, `jpg`, `auto` | `auto` negotiates WebP or PNG from each client's `Accept` header (`Vary: Accept`); a signed URL created with `auto` negotiates on every load. |
A 16:9 hero: `width: 1024, height: 576, trim: true, padding: 6`. A thumbnail on a grey card: `size: "thumb", background: "#f4f4f4"`. A brand color the presets do not have: `color: "#1a2b3c"` (responses echo `#1a2b3c`; a hex equal to a preset swatch is that preset). The same vehicle, view and color is one render however it is sized, so different boxes of the same car stay fast.
## Authentication
The catalog server needs no credential. The account server signs in through browser OAuth managed by the host, or in Claude Code through an API key the user saved in the plugin's options (optional; kept in the system's secure credential store and sent only to the Car Image server). No API-key environment variable is required. If the account tools work, use that connection; it is the only credential you need. If they ask for sign-in, follow [Before the account is connected](#before-the-account-is-connected): the `authenticate` tool, or `/mcp` in Claude Code (select `plugin:car-image:car-image` and authenticate), or a key saved under `/plugin` → the Car Image plugin → **Configure options**.
You never handle a key yourself: do not ask for one in the conversation, and do not read one from environment variables, `.env` files, shell profiles or a CLI's config to put in a command or a header. Code you write for the user's own application gets its key from that application's secret settings, which the user manages (the `car-image-sdk` skill). Keys look like `cimg_…`; a user without one runs `npx @meterapp/car-image login` (a browser device flow) or creates one at [the dashboard](https://carimage.dev/dashboard?ref=plugin).
**Never** put a key in a URL, in HTML, in client-side JavaScript, in a commit, in a log line, in a screenshot, in a config file that gets committed, or in a prompt. Do not guess or fabricate keys.
Browser code must never hold the key. Mint signed URLs server-side instead — that is what `create_car_image_urls` is for.
## Work the user can trust
1. **Look up before you spend.** Every vehicle, once, before its first render. Rendering the wrong vehicle still costs a credit.
2. **Ask when it is ambiguous.** "Civic" spans four decades and a dozen bodies. On `low` confidence, or when the candidates are different cars, show them and let the user pick.
3. **Reuse the id and the parameters.** One lookup serves every view, paint and size of that car, and an identical request hits the cache, so repeats stay fast.
4. **Write real alt text.** "2024 Porsche 911, side view, red" — not "car image".
5. **These are renders, not photographs.** They are generated product visuals of a make, model and year. Never claim an options package or an individual listed vehicle is depicted exactly. For a used-car listing, say the image represents the model, not that car.
6. **Close the loop.** When the user judges a render, point them at the Recent images card on [the dashboard](https://carimage.dev/dashboard?ref=plugin#feedback), where a render is rated; there is no tool for it.
7. **Say so, don't substitute.** When the catalog lacks the vehicle, say it does not carry it and stop. Never render the nearest thing in its place, and never present another car as the one that was asked for.
## When something fails
Every JSON failure is an RFC 9457 `application/problem+json` document with `detail` and `request_id`. Keep the `request_id` — it is what support needs.
| Status | Meaning | Do |
| --- | --- | --- |
| `Input validation error` | The tool refused an argument before the API saw it: an unknown view or format, a value out of range, a vehicle id together with a make. Nothing was charged. | The message names the argument and the rule: fix that one and call again. The type is rarely the cause, because a host that sends arguments as text is read as it meant: `"true"` is the flag, `"960"` the number, and the JSON of `images` the list. Only a `car-image mcp` server older than CLI 1.9.4 refuses a flag or a list sent that way: leave `trim`, `padding` and `renew` out there, and when it refuses `images` itself use `get_car_image` with `vehicle`, `view`, `color`, `width` and `height`. Do not abandon the render. |
| 400 | Invalid parameter (`parameter` names it, `retryable: false`) | Views, `fit` and `format` are fixed enums; `color` is a preset name, a CSS color name or a hex; `vehicle` cannot be sent with make, model or year; `padding` needs `trim`; `jpg` cannot be `transparent`. Fix the value with `list_image_options` or `resolve_vehicle`; sending it again unchanged always fails. |
| 401 | Missing or invalid credential | For the plugin, ask the user to reconnect through the host’s MCP sign-in controls (`/mcp` in Claude Code), or to replace the key saved in the plugin's options. For code in their project, the application's key is missing or revoked: the user fixes it where the application keeps its secrets. Never guess a key. |
| 402 | Out of credits, or `code: "plan_vehicle_limit"` (the request named more new distinct vehicles than the plan's monthly cap allows; `scope`, `plan`, `vehicles_this_month`, `vehicles_per_month`, `requested`) | **Stop and ask the human** to top up at [the dashboard](https://carimage.dev/dashboard?ref=plugin#billing) or with `car-image billing`, or, for a plan limit, tell them the plan and the cap. Never buy credits or change a plan on your own. |
| 404 | Vehicle not in the catalog under that name (`code: "vehicle_not_found"`), or an unknown vehicle id | You rendered by name: look the vehicle up instead. Read the problem's `suggestions` first (up to five real catalog vehicles with ids, closest first) and retry with `vehicle: <id>` when one is plainly the car, or show them to the user; call `resolve_vehicle` when they are empty. Do not send the same name again in another view or color. If it is really missing, say so and stop. Do not invent vehicles or substitute a different one. |
| 429 | Rate limited (per key, or per account with `code: "account_rate_limited"`), or `code: "account_generation_cap"` (the account used its plan's share of today's render budget; cached images keep serving) | Wait `Retry-After` seconds, retry once. Never hammer; a generation cap resets at midnight UTC (`reset_at`), so do not retry cold renders before then. |
| 502/503 | Render or upstream failure | Credits are refunded. Retry once later; report with the `request_id`. |
The full table, including every `type` URI, is in [references/errors.md](references/errors.md).
## Never do these on your own
- Render a vehicle you have not looked up, or send a name again after a `404`.
- Buy credits, or subscribe to, change or cancel a plan, or touch billing. A `402` of either kind is a question for the human, not a purchase to make.
- Render a large batch the user did not ask for. Confirm the count and the cost first.
- Create a 3D model without saying it costs 100 credits and confirming.
- Put the API key anywhere a browser, a repository or a log can see it.
- Claim an image is a photograph of a specific vehicle.
## Make logos
`get_make_logo({ make: "toyota", width: 256, trim: true })` returns a catalog make logo inline; `car-image logo --make Toyota --width 256 --trim --out toyota-logo.png` and `client.getMakeLogo({ make: "toyota", width: 256 })` save one. REST: `GET /api/v1/images/logo?make=toyota`. The tool is on `?toolset=all`, which the plugin configures; a connection on the bare URL has no logo tool (the `car-image-mcp` skill).
- **1 credit per delivered logo**, including cache hits; a failed delivery is refunded. Logos do not count as distinct vehicles. On a `402`, stop and ask the human.
- Takes the make and the image transforms (`size`, `width`/`height`, `fit`, `background`, `trim` with `padding`, `format`; `auto` returns PNG over MCP). No model, year, view, paint color or custom prompt, and no signed URL: download the file and host it for a site. An unknown make is a `404`.
- Logos are prompt-generated and can be inaccurate: look at one before using it. Do not use the vehicle image or URL tools for logos.
Logos are third-party trademarks. They are served for referential display of the make they identify, no license is granted, and they sit outside every VehiclesDB indemnity — see the [API terms](https://carimage.dev/terms?ref=plugin#logos) and the [logo docs](https://carimage.dev/docs/logos?ref=plugin).
## Product help and billing links
Questions about pricing, licensing, terms or an Enterprise agreement, and a human's request to buy credits or open billing settings, belong to the `car-image-support` skill (`search_help`, `get_pricing`, `create_checkout_link`, `get_billing_link`). A link is never a purchase: the human completes it in the browser. [Help center](https://carimage.dev/help?ref=plugin).
Referenced files: 2 — file references are metadata, not package contents.
car-image-mcp17.7 KB
---
name: car-image-mcp
description: Connect an agent, IDE or chat app to the Car Image API over MCP, and fix it when the tools do not appear. Covers the hosted HTTP server, the catalog server that needs no sign-in (lookups and free previews), the local stdio alternative, browser OAuth sign-in for plugin installs and chat-app connectors, the optional API key saved in the plugin's options in Claude Code, API-key setup for manual connections in Codex, Cursor, Claude Desktop and generic MCP hosts, what each of the seven core tools costs (catalog lookups, VIN decoding, images, signed URLs, the account) and which ten more ?toolset=all adds (make logos, 3D models, image options, the API reference, pricing, help and billing links), and how to diagnose a 401, a missing server or an empty tool list. Use for setup, configuration and connection troubleshooting; do not use for calling the API from code (car-image-sdk) or for image workflows once the tools already work (car-image).
---
# Connecting over MCP
Two servers expose the same tools. Prefer the hosted one — there is nothing to install and nothing to keep up to date.
- **Hosted (recommended):** `https://carimage.dev/api/mcp`, Streamable HTTP, with browser OAuth sign-in in supported hosts
- **Local stdio:** `npx @meterapp/car-image@latest mcp`, which uses the key `car-image login` saved for the CLI
A third, the **catalog server** at `https://carimage.dev/api/mcp/catalog`, needs no account at all: `resolve_vehicle`, `search_vehicles`, `list_image_options`, `get_pricing`, `search_help` and `describe_api`, plus `preview_car_image`, the picture a vehicle's page on carimage.dev already shows, free. It renders nothing and reads no account, so it never asks anyone to sign in. The plugin connects it next to the hosted server, so an install can look cars up and show previews before the user signs in.
Both come in two toolsets. The default, `core`, is the seven core tools an agent needs to find a vehicle, render it and watch the balance: `resolve_vehicle`, `search_vehicles`, `decode_vin`, `check_vehicles`, `get_car_image`, `create_car_image_urls` and `get_account`. Make logos, 3D models, image options, the API reference, pricing, help and billing links are ten more with `?toolset=all` on the hosted URL or `--toolset all` for `car-image mcp`, twenty-six tools in all. Fewer tools cost the agent less context and make the right one easier to pick, so opt in when the agent should also fetch logos, make 3D models or answer product and billing questions; the plugin does.
## Plugin installs: sign in with your browser
The `car-image` plugin configures two hosted servers: **`car-image-catalog`**, the catalog server, which works at once, and **`car-image`**, the hosted server with `?toolset=all` (the skills teach the logo, 3D, pricing and help tools beyond the core seven), which signs in. **No API key or environment variable is needed.** Sign in when you first want an image: in Claude Code, the agent can start it with the `authenticate` tool Claude Code lists for a server that needs sign-in (`mcp__plugin_car-image_car-image__authenticate`) and hand you the link, or open `/mcp`, select `plugin:car-image:car-image`, and authenticate; complete Car Image sign-in and approve the connection in your browser. Codex and Cursor expose sign-in through their MCP connection controls. Then skip to [Verifying](#verifying).
**Or save an API key (Claude Code, since plugin 1.12.0).** For a machine with no browser, or a shared service account, the plugin takes an optional **Car Image API key**. Claude Code asks for it when the plugin is enabled; to set, change or clear it later, open `/plugin`, select the Car Image plugin, choose **Configure options**, and restart. Recent Claude Code versions also take it from a terminal: `claude plugin configure car-image@meterapp --values-stdin` (for a directory install, use the plugin id `claude plugin list` shows), then type `{"api_key":"cimg_…"}` and end the input with Ctrl-D, which keeps the key out of shell history. Claude Code keeps the key in the system's secure credential store and sends it only to the Car Image server, as the `X-Api-Key` header; left empty, browser sign-in applies. The human enters the key there themselves. Never ask for it in the conversation, and never copy one from environment variables, `.env` files or another tool's config to fill it in.
The plugin never sets an `Authorization` header. Claude Code treats a configured `Authorization` header as the whole credential and turns off OAuth fallback, whatever its value, so a saved key travels as `X-Api-Key` instead, and a signed-in token takes precedence when both are present. Do not add an `Authorization` header to fix a plugin sign-in problem.
Upgrading from a release before 1.10.1? Update the plugin, restart the host, and sign in. If you also added a manual server with an API-key header, use the plugin-provided server; the manual connection still needs its configured key.
## Chat apps: add it as a connector
A chat app that takes remote MCP servers (custom connectors in Claude, connectors or apps in ChatGPT) needs only the URL: `https://carimage.dev/api/mcp`, or `https://carimage.dev/api/mcp?toolset=all` for all twenty-six tools (make logos, 3D models, pricing and help too). The app discovers the sign-in itself (`/.well-known/oauth-protected-resource`), opens Car Image in the browser, and the human approves `images:read` and `account:read` on the consent screen. There is no key to paste, and a connected app can never buy anything: it holds no `billing:write`.
Once connected, a request for a picture of a car is two tool calls: `resolve_vehicle` (free), then `create_car_image_urls` with the id it returned (1 credit); paste the returned `markdown` directly into the final reply. The `car-image` skill has the conversation workflow.
## Manual hosted setup with OAuth
### Claude Code
```bash
claude mcp add --transport http car-image https://carimage.dev/api/mcp
```
Then open `/mcp` and authenticate. Recent Claude Code versions also support `claude mcp login car-image` from a terminal. To have the lookups and previews before signing in, add the catalog server too; it needs no sign-in:
```bash
claude mcp add --transport http car-image-catalog https://carimage.dev/api/mcp/catalog
```
For all twenty-six tools, use `"https://carimage.dev/api/mcp?toolset=all"` as the URL (quote it: the shell would otherwise treat `?` as a glob).
### Other OAuth-capable hosts
Use this in the host’s MCP configuration, such as `.cursor/mcp.json`:
```json
{
"mcpServers": {
"car-image": {
"type": "http",
"url": "https://carimage.dev/api/mcp"
}
}
}
```
Sign in using the host’s MCP controls. Set `"url": "https://carimage.dev/api/mcp?toolset=all"` when the agent should also have make logos, 3D models, pricing and help. Codex plugin installs already configure that URL.
## Optional API-key or local stdio setup
For a host without OAuth, or an explicit API-key connection, the human gets a key: `npx @meterapp/car-image login` (a browser device flow; it prints the key once and saves it for the CLI) or [the dashboard](https://carimage.dev/dashboard?ref=plugin). Keys need the `images:read` scope.
- **Claude Code:** save it in the plugin's options, as in [Plugin installs](#plugin-installs-sign-in-with-your-browser). No second server is needed.
- **Another host:** the human enters the key in that host's own MCP settings, as an `X-Api-Key` header (or `Authorization: Bearer`, which some hosts then use instead of OAuth), wherever that host keeps secrets. `car-image agent-config --host claude-code|claude-desktop|cursor|chatgpt|generic` prints the configuration for each host. Never put the key in a project file that gets committed, in a URL or in a prompt, and do not add a key header to the plugin's `.mcp.json`.
Local stdio instead, using the key `car-image login` saved:
```bash
claude mcp add car-image-local -- npx -y @meterapp/car-image@latest mcp
```
Append `--toolset all` after `mcp` for all twenty-six tools.
## The tools
### Core — every connection has these seven
| Tool | Costs |
| --- | --- |
| `resolve_vehicle` | free |
| `search_vehicles` | free |
| `decode_vin` | free |
| `check_vehicles` | free |
| `get_car_image` | 1 credit |
| `create_car_image_urls` | 1 credit per URL |
| `get_account` | free |
Two cost credits. The four lookups come first in the table because they come first in use: `resolve_vehicle` turns what was asked for into the catalog vehicle and its id, with a confidence of `high`, `medium` or `low`; `search_vehicles` lists a make's models and a model's years; `decode_vin` turns a full or partial VIN into the catalog vehicle; `check_vehicles` checks a list of up to 100 at once and answers each entry as a match with its id, a suggestion, a miss or an invalid entry (longer lists: `car-image check <file>` or `client.checkVehicles`). `get_car_image` and `create_car_image_urls` then take `vehicle` (that stable `veh_…` id) in place of make, model and year, `color` as a preset or any hex, and `fit`, `background`, `trim` and `padding` alongside width and height, so an agent can ask for exactly the box a layout needs, and `format` accepts `auto` (a signed URL negotiates WebP or PNG per viewer; an inline `get_car_image` delivers PNG for it). `create_car_image_urls` also honors `renew` and `renew_days`, and takes `idempotency_key`: pass one whenever the call might be repeated, because a retry with the same key and arguments replays the first result (`idempotent_replayed: true`) instead of billing again. `get_account` reports the balance, the plan in force (`data.plan`) and the key's scopes, whichever credential the connection holds.
### Ten more with `?toolset=all` or `--toolset all`
| Tool | Costs |
| --- | --- |
| `get_make_logo` | 1 credit |
| `create_3d_model` | 100 credits; free once the account owns the vehicle and color |
| `get_3d_model` | free |
| `publish_3d_model` | free |
| `list_image_options` | free |
| `describe_api` | free |
| `get_pricing` | free |
| `search_help` | free |
| `create_checkout_link` | free; a link the human completes in the browser |
| `get_billing_link` | free; a link the human completes in the browser |
`get_make_logo` takes a make and the image transforms and returns an inline logo: no model, view or paint, no signed URL and no trademark license. `create_3d_model` is 100 credits at creation, free for a vehicle and color the account already owns (`billing.already_owned`), and takes `publish`; `get_3d_model` polls it and returns `public` once published; `publish_3d_model` hosts a model at key-free URLs with a two-line `<car-3d>` embed, or takes it down with `unpublish: true` (the `car-3d` skill). `list_image_options` lists every valid view, color, size, fit mode, background and format, and `describe_api` explains any REST endpoint from the OpenAPI document. `get_pricing` and `search_help` answer product questions from the published sources, and the two link tools prepare a purchase or open billing settings without charging anything (the `car-image-support` skill). The plugin connects with `?toolset=all`, so its skills can name these; on the bare URL an agent has the core seven, and its instructions tell it to say so and stop when the catalog lacks a vehicle.
## Verifying
Ask the host to list tools (`/mcp` in Claude Code and Codex). With the bare hosted URL or a plain `car-image mcp` you should see **seven** tools under `car-image`; with `?toolset=all` (what the plugin ships) or `--toolset all`, **twenty-six** tools. Seven where you expected twenty-six is not a fault — the URL simply has no `?toolset=all`. A plugin install also shows `car-image-catalog` as connected, with `resolve_vehicle`, `search_vehicles`, `preview_car_image`, `list_image_options`, `get_pricing` and `describe_api`, whether or not anyone has signed in.
A free end-to-end check that spends nothing:
> "Use search_vehicles to list the model years of the Porsche 911."
Then a real one, which costs 1 credit:
> "Show me a red 2024 Porsche 911, front three-quarter."
A working connection answers it in two calls, `resolve_vehicle` and then `create_car_image_urls` with the vehicle id, and the image is in the reply. `get_account` (free) says which account and plan the connection is using, whichever credential it holds.
To check that the server is reachable at all, with no credential, from a terminal:
```bash
curl -sS -i -X POST https://carimage.dev/api/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
A `401` with `WWW-Authenticate: Bearer realm="Car Image API", resource_metadata="…"` is the healthy answer: the server is up and asking for sign-in. Anything `5xx`, or no answer, is an outage. The same request to `https://carimage.dev/api/mcp/catalog` answers `200` with the catalog server's tools, no credential needed. `car-image doctor` tests the REST endpoints with the key the CLI saved.
## When it does not work
**No `car-image` server in the list.** The host did not load the config. Restart it — most hosts read MCP configuration only at startup. Check you edited the file the host actually reads (`claude mcp list` shows what Claude Code sees).
**Plugin needs authentication, zero tools, or a connection error.** Needing authentication is the normal state of `car-image` before the first sign-in; `car-image-catalog` keeps answering lookups and previews meanwhile. Open the host’s MCP controls and sign in. In Claude Code, use `/mcp`, or let the agent call the `authenticate` tool and open the link it gives you. If it says the configured Authorization header was rejected, update the plugin and restart: releases before 1.10.1 required an environment key and prevented OAuth fallback. Check that you selected the plugin server rather than an older manually configured server.
**A key is saved in the plugin's options, and Claude Code still asks for sign-in.** The server refused the key (mistyped, or revoked in the dashboard) and Claude Code fell back to browser sign-in, which is working as designed. Save a current key under `/plugin` → the Car Image plugin → **Configure options**, or clear the field and sign in, then restart.
**A manual API-key connection in another host returns 401.** Check the key in that host's MCP settings; the host must be restarted after it changes. Create a new key in the dashboard if the old one was revoked. An unauthenticated 401 with `WWW-Authenticate: Bearer … resource_metadata="…"` is the normal start of OAuth, not by itself a failed sign-in.
**Seven tools, but no `create_3d_model`, `get_make_logo` or `search_help`.** The connection uses the default toolset, which is working as designed. Add `?toolset=all` to the URL, `https://carimage.dev/api/mcp?toolset=all` (or `--toolset all` to the stdio command), and restart the host.
**`400 Unknown toolset`.** The query value must be `core` or `all`; anything else is rejected before any tool is listed.
**Tools appear but every call fails with 402.** The account is out of credits (a 3D model needs 100 at once, so it is the usual cause), or the problem carries `code: "plan_vehicle_limit"` and the request named more new distinct vehicles than the plan allows this month (Free 100, Pro 2,500, Business 15,000). Report the balance, or the plan and the cap, and let the human decide; never buy credits or change a plan automatically.
**A call fails with `Input validation error`.** The tool refused an argument before the API saw it, and nothing was charged. The message names the argument and the rule. A host that holds no typed schema for a tool sends every argument as text, and both servers read that as it was meant: `"960"` is the number, `"true"` the flag, and the JSON of `images` the list. A `car-image mcp` server older than CLI 1.9.4 refuses a flag or a list sent that way (`expected boolean, received string`; `expected array, received string`): update the CLI.
**A vehicle the catalog has answers `404`.** The agent rendered by a name from memory. It should look the vehicle up first (`resolve_vehicle`) and render by the `vehicle` id; the `car-image` skill has the two steps.
**Calls fail with 403.** The key lacks `images:read`. Create a correctly scoped key.
**Everything is slow the first time.** The first render of a given make/model/year/view/color takes a few seconds; after that it is cached and instant. This is expected, not a connection problem.
**A tool returns 429.** The per-key limit is 120 requests per minute on Free and Pro, 600 on Business and 1,200 on Enterprise, and every account also has a limit across all its keys (`code: "account_rate_limited"`). Wait the `Retry-After` seconds. If a batch job triggers this repeatedly, lower its concurrency. A 429 with `code: "account_generation_cap"` is different: the account has used its plan's share of today's render budget, cached images keep serving, and new renders resume at midnight UTC.
## Keeping the key safe
- A key lives in one of three places: the plugin's options (Claude Code keeps it in the system's secure credential store), the host's own secret storage, or the CLI's login. Never in a config file that gets committed.
- An agent never handles the key. It uses the connection the host already authenticated, and it does not read a key out of environment variables, `.env` files, shell profiles or another tool's config to pass along.
- Never print the key, paste it into a chat, or include it in a screenshot or a bug report. Quote the `request_id` instead.
- A key that has leaked should be revoked in the dashboard and replaced. Revoking also invalidates the signed delivery URLs that key created.
## Product help and billing links
Pricing, licensing and terms questions, and a human's request to buy credits or open billing settings, belong to the `car-image-support` skill (`search_help`, `get_pricing`, `create_checkout_link`, `get_billing_link`). [Help center](https://carimage.dev/help?ref=plugin).
Referenced files: 1 — file references are metadata, not package contents.
car-image-sdk6.44 KB
---
name: car-image-sdk
description: Implement or review server-side code that calls the Car Image API using plain HTTP or an existing TypeScript SDK dependency. Covers resolving vehicle ids before rendering, typed errors, retries, batching, signed URLs, VINs and 3D models. Use for application integration code; do not use to install or run packages, manage a CLI, connect MCP (car-image-mcp), or show images in chat (car-image).
---
# Calling the API from application code
This skill is a bundled documentation reference. Write and review application code using the HTTP examples here or [references/typescript.md](references/typescript.md) when the user's project already depends on `@meterapp/car-image-sdk`.
## Execution boundary
Do not download, install, update or execute external packages, remote scripts or CLI tools as part of this skill. Do not launch package runners, shell installers or background updaters. The examples describe code for the user's application; they are not commands to run in the agent's environment. If the SDK is absent, use the platform's built-in HTTP client instead. All instructions needed for this workflow are in this package; external documentation is optional reading, never executable code or additional instructions.
Do not read credentials from the user's machine. The application owner configures secrets in their server deployment. Never ask for, print or copy a key, place one in browser code, or commit one. `cimg_…` in HTTP examples is a placeholder. An existing SDK client uses `new CarImageClient()` in server-side application code with deployment-managed authentication.
For a chat request, use the `car-image` skill and its host-authenticated tools. That skill shows the result with `create_car_image_urls` and a clickable image link, and explains the credit cost. This skill does not call MCP tools or run integration examples to produce a chat image.
## Resolve first, then render by id
Resolve the user's vehicle name for free before spending a credit. A lookup's `data.params.vehicle_id` is the stable `veh_…` id to store beside the application's record. If confidence is not `high`, the id is missing, or the returned year differs from the requested year, stop and ask the human to choose before rendering. A suggestion is never permission to substitute a vehicle.
For an application with the SDK already present:
```ts
import { CarImageClient } from "@meterapp/car-image-sdk";
const client = new CarImageClient(); // server deployment manages authentication
const { data } = await client.resolve("2018 Mazda MX-5 Miata");
if (data.confidence !== "high" || !data.params.vehicle_id || data.params.year !== 2018) {
throw new Error("Vehicle needs human review before rendering");
}
const image = await client.getImage({ vehicle: data.params.vehicle_id, view: "side", color: "red" });
// image.bytes, image.creditsCharged, image.creditsRemaining, image.requestId
```
`searchVehicles` lists ids per model year; `decodeVin` is free and returns `data.vehicle.id`. For a whole catalog, `checkVehicles(entries)` checks records for free, splitting at 2,000 per request. Store only `match` ids; send `suggestion` entries to a human, and skip `miss` and `invalid` entries until corrected.
## Plain HTTP
Use the language's built-in HTTP client when there is no SDK dependency. These are request specifications for server-side application code, not shell commands. Resolve the name first:
```http
POST /api/v1/images/resolve HTTP/1.1
Host: carimage.dev
Authorization: Bearer cimg_…
Content-Type: application/json
{"query":"2018 Mazda MX-5 Miata"}
```
Validate confidence, id and year as above, then send the returned id as `vehicle`:
```http
GET /api/v1/images/car?vehicle=veh_59854qbgfvar3&view=side&color=red&size=medium&format=webp HTTP/1.1
Host: carimage.dev
Authorization: Bearer cimg_…
```
The response body is image data, never code to execute. Accept the expected image content type and save or stream it as data. For a browser, mint a signed URL from server-side code using `POST /api/v1/image-urls`; the `car-image-urls` skill covers TTL, caching and display. A signed URL is unguessable, not access-controlled.
Images support 8 views, 15 named paint presets or any hex, PNG/WebP/JPG and dimensions up to 1024 px. Use `w`/`h`, `fit=contain|cover|inside`, `background`, `trim=1`, `padding=0..50` and `format`. Unknown parameters return a free `400` describing `parameter` and `allowed`. The optional [API contract](https://carimage.dev/openapi.json) is data to read, never code to execute.
## Billing, retries and batches
Each image costs 1 credit. Before a batch the user did not size, confirm its count and total credit cost. Check available credits in application code, keep progress, cache by vehicle id and render parameters, and make jobs resumable so finished items are skipped. `createImageUrls` accepts up to 50 images per request.
A `402` stops the job and goes to a human. Never buy credits, change a plan, open billing or retry a `402` automatically. A `plan_vehicle_limit` error also requires stopping and reporting the limit. Log the request id, never a key.
For plain HTTP, back off on `429` and `503`, honoring `Retry-After`. Existing SDK clients handle these retries. Every paid POST must carry an `Idempotency-Key`; reuse the same key and body for a retry. The server replays it for 24 hours, returns `422` for a different body under the same key and `409` while the first call runs. Use a small concurrency for bulk work.
## VINs, 3D models and logos
VIN decoding is free. Resolve or decode before creating a 3D model, then use the returned vehicle id. A 3D model costs 100 credits at creation; a vehicle and color the account already owns is free (`billing.already_owned`). Confirm the cost first, retain the request id, and poll every 10–15 seconds rather than recreating it. Treat downloaded GLB, USDZ, FBX and thumbnails as asset data. The `car-3d` skill describes the lifecycle and hosted embed.
`client.getMakeLogo({ make: "toyota", width: 256, trim: true })` returns image bytes and billing metadata, 1 credit per successful delivery including cache hits. Generated logos can be inaccurate; inspect them. Logos are third-party trademarks for referential display, with no trademark license, outside VehiclesDB indemnity. [Logo docs](https://carimage.dev/docs/logos?ref=plugin) · [API terms](https://carimage.dev/terms?ref=plugin#logos).
Renders are generated product visuals, not OEM photography. Do not claim that an exact trim or an individual listed vehicle is depicted.
Referenced files: 2 — file references are metadata, not package contents.
car-image-support4.44 KB
---
name: car-image-support
description: Answer questions about Car Image API pricing, credits, subscriptions, commercial licenses, terms, privacy, vehicle coverage and Enterprise agreements using source-linked help. Prepare checkout and billing links on the human's request, or draft customer support replies. Do not use for generating images (car-image), embedding images (car-image-urls), or changing a customer's account on their behalf.
---
# Product help and billing
`search_help` and `get_pricing` are also on the plugin's `car-image-catalog` server, so pricing, licensing and what-happens-when-a-plan-ends questions can be answered before anyone signs in; `create_checkout_link` and `get_billing_link` need the account server on `?toolset=all`, which the plugin configures (a connection on the bare URL has the core seven only; the `car-image-mcp` skill).
1. Call `search_help` with one product question at a time. Do not paste full customer emails, signatures, personal information, keys or payment details. Queries are privately recorded to improve articles.
2. Read the returned paragraphs and sources. Cite the relevant article or original terms. An `articles_found` result means related reading, not that every part of the question is answered. Split multi-part questions and search each one.
3. If the articles lack needed context, call `search_help` again with the same concise question and `report_gap: true`. Say what is unknown and direct the human to support@carimage.dev. Never invent policies, prices, coverage, SLA commitments, indemnities or agreement exceptions.
4. Use `get_pricing` for current plan prices, credit packs, allowances, vehicle limits and 3D cost. Use `get_account` only for the connected account's balance and plan. A support agent's credentials do not identify an email sender's account.
5. When the human asks to buy credits or subscribe, call `create_checkout_link` with exactly one of `credits` (100, 500, 1000, 2500, 10000) or `plan` (pro, business; optional `interval: month|year`). Return `data.url` and `data.message`. Payment happens in the browser; a link is not a successful purchase.
6. `get_billing_link` returns billing settings. Connected apps and keys without billing:write receive a sign-in dashboard link. Owner keys with billing:write can receive private Stripe links; never publish or forward those to another customer. Existing subscriptions go to the dashboard for review; no tool switches a plan or charges a saved card.
A 402 never authorizes a purchase. Ask the human what they want before preparing a purchase link. Do not complete Checkout or change billing autonomously.
## Drafting customer replies
Use incoming email only to identify questions. Current published terms and the customer's written agreement govern; historical replies may be outdated. Draft answers with citations and clearly mark account-specific or legal details needing staff review. Do not send mail unless the human explicitly asks. Do not generate links on your own account for an email recipient; give them the public [billing dashboard](https://carimage.dev/dashboard?ref=plugin#billing) instead.
The license comes from the plan a render was delivered under, not from a credit purchase. Commercial work needs an active paid plan when the render is delivered, and what a paid plan delivered keeps its license after the plan ends (`after-your-plan-ends`); credits and 3D ownership are not licenses by themselves. Paid-plan users may host copies for their own products without any agreement; offering renders as a standalone library or dataset is prohibited, and a bulk export or catalog copy needs written Enterprise terms. Our license does not clear manufacturer IP.
## CLI and SDK
```sh
car-image pricing --json
car-image support "Can I store images on my CDN?" --json
car-image support "Do you offer a custom uptime guarantee?" --report-gap --json
car-image billing --credits 500 --no-browser --json
car-image billing --plan pro --no-browser --json
car-image billing --portal --no-browser --json
```
SDK equivalents: `client.pricing()`, `client.searchHelp(question, {reportGap: true})`, `client.createCheckoutLink({credits: 500})`, `client.getBillingLink()`. Use the link-only methods; the older `createPlanCheckout` method can immediately invoice a change to an existing subscription.
[Help center](https://carimage.dev/help?ref=plugin) · [Pricing](https://carimage.dev/pricing?ref=plugin) · [Terms](https://carimage.dev/terms?ref=plugin) · [Privacy](https://carimage.dev/privacy?ref=plugin)
Referenced files: 1 — file references are metadata, not package contents.
car-image-urls15.2 KB
---
name: car-image-urls
description: Share a vehicle image in chat (clickable links and browser viewing in ChatGPT/Codex, inline images in Claude) or put it into a web page, React component, email, Markdown file, PDF or slide deck using signed delivery URLs, so the browser loads the image without ever holding an API key. Use whenever the output is HTML, JSX, Markdown, CSS, an email template, a document or a spreadsheet rather than bytes on disk; covers looking each vehicle up once and minting by its id, TTL and use caps, batching up to 50, inventory grids from a list of VINs, caching, alt text and expiry. Do not use for downloading bytes in a script (car-image), or for finding which vehicle to render (vehicle-catalog).
---
# Signed delivery URLs
A signed URL is a key-free link to one rendered vehicle image. You mint it server-side; the browser, email client or PDF reader loads it directly. **The API key never goes where a browser can read it: a page gets a signed URL, or a copy of the file you serve yourself.**
## Chat replies
For a chat image, resolve the vehicle first, then mint once with `ttl_seconds: 604800`, `max_uses: 0` and renewal off unless requested. The `car-image` skill covers random-car selection and the chat workflow.
In ChatGPT and Codex, show each exact returned `data[].url` as a normal clickable Markdown link, for example `[View the vehicle image](https://example.com/image.png)` with the returned URL substituted. Do not use Markdown image syntax or paste the tool's image `markdown` into these chats, even if generic tool text recommends it. Open that same URL with an available browser-opening tool, preferably in the in-app browser (for example, `open_in_codex` with a browser target). Retain the clickable link in the final reply whether opening succeeds or not. If no browser tool is available, just provide the link. Do not claim the browser opened unless the tool confirms it.
In Claude, keep rendering inline: paste each returned `data[].markdown` outside code fences. If only `url` is returned, use `` with the exact returned URL. Do not automatically open a browser. Other hosts may use inline images when supported, otherwise a clickable link.
Include a short vehicle caption and expiry. Reuse unexpired URLs; do not download the image or make another paid image call to work around chat display. These host-specific rules apply to chat replies; website and document embeds still use image markup.
## Why not just call the image endpoint from the page
`GET /api/v1/images/car` needs `Authorization: Bearer cimg_…`. Anything in a browser — an `<img src>`, a `fetch` in client-side JavaScript, a CSS `background-image` — is readable by the user and by anyone they share the page with. Putting the key there leaks it permanently.
Signed URLs solve this: the signature authorizes exactly one image for a limited time, and carries no key.
## The call
Look each vehicle up first (`resolve_vehicle`, `search_vehicles` or `decode_vin`; all free, the `vehicle-catalog` skill) and mint by its id. A name from memory can miss a car the catalog carries, and in a batch one wrong name fails the whole call.
With MCP connected, use `create_car_image_urls`. Over REST it is `POST /api/v1/image-urls`:
```json
{
"images": [
{ "vehicle": "veh_78qtwrgh37bkr", "view": "side", "color": "red", "width": 768, "height": 432, "trim": true, "format": "auto" }
],
"ttl_seconds": 86400,
"max_uses": 0
}
```
An entry may spell the vehicle out instead, in the catalog's spelling:
```json
{
"images": [
{ "make": "Porsche", "model": "911", "year": 2024, "view": "side", "color": "red", "width": 768, "height": 432, "trim": true, "format": "auto" }
],
"ttl_seconds": 86400,
"max_uses": 0
}
```
The MCP tool also returns ready-to-display `markdown` for each image. Each REST entry returns `id`, `url`, `expires_at`, `max_uses`, `renews_until` and the normalized vehicle (which opens with `vehicle_id` and echoes `fit`, `background`, `trim` and `padding`). `"vehicle"` and make, model and year together are a `400`, and `"color": "#1a2b3c"` is any paint at the same price.
- **`images`** — 1 to 50 per call. Body limit is 64 KiB; split larger jobs into batches of 50. Each entry takes the same sizing options as `get_car_image`: `size` or `width`/`height` (delivered up to 1024, a larger size clamped; both together return exactly that box), `fit` (`contain` default, `cover`, `inside`), `background` (`transparent` default, a CSS color name such as `white`, or hex), `trim` with `padding` (0–50 %), and `format` (`png`, `webp`, `jpg`, `auto`). The `car-image` skill has the full table.
- **`ttl_seconds`** — 60 to 604800 (7 days). Default 3600.
- **`max_uses`** — `0` means unlimited loads until expiry. A positive number caps redemptions and returns `410` afterwards.
- **`renew`** — `true` makes the URL auto-renew: the first load in each further window of `ttl_seconds` bills one more credit and keeps it alive, until `renew_days` (1–365, default 365) from creation. Requires `max_uses: 0`. See "Surfaces that outlive a TTL" below.
- **`Idempotency-Key` header** — send one (1–255 characters of letters, digits, `.` `_` `:` `-`) whenever the call might be repeated: a job queue, a build step, a retried request. The same key with the same body within 24 hours replays the first response (`Idempotent-Replayed: true`) instead of minting and billing a second batch; the same key with a different body is a `422`; a retry that overtakes the original still in flight is a `409` with `Retry-After`. The SDK sends a generated key on every `createImageUrls` call (pass `idempotencyKey` to choose it), the CLI takes `--idempotency-key`, and the MCP tool takes `idempotency_key` as an argument (a replay comes back with `idempotent_replayed: true`).
## What it costs
**1 credit per URL, charged when the URL is created.** Loading it is free, however many times, until it expires. Ten URLs cost 10 credits (1¢) whether they are loaded once or a million times.
So: mint once, cache the URL, reuse it. Do not re-mint the same image on every page render — that bills a credit every time.
## Choosing a TTL
The TTL is a cache lifetime, not a security boundary — anyone with the URL can load it until it expires. Match it to how the page is served.
| Surface | TTL | Why |
| --- | --- | --- |
| Static site, build-time generation | 7 days (`604800`), re-mint on each build | Longest window; the build owns the refresh. |
| Server-rendered page, cached in your DB | 1-7 days | Store `url` and `expires_at`; re-mint when `expires_at` is near. |
| Per-request SSR with no storage | 1 hour (default) | The page outlives the URL only if the user leaves the tab open. |
| Email | 7 days, `max_uses: 0`, `renew: true` | Mail is opened days, months or a year later. A renewable URL keeps working at one credit per opened week; a plain 7-day URL is a broken image by week two. |
| CMS page or PDF nobody re-publishes | 7 days, `renew: true` | Same reasoning as email: the surface is rendered once and read for a long time. |
| One-off share link | Short TTL plus `max_uses` | The only case where a use cap helps. |
**Never mint a URL inside a React render, a `useEffect`, or a hot request path without caching.** That is a credit per call.
## Surfaces that outlive a TTL
An email, a PDF or a page in a CMS nobody re-publishes is rendered once and opened whenever the reader gets to it. For those, mint with `renew: true`:
```json
{ "images": [{ "make": "Porsche", "model": "911", "year": 2024, "view": "front-3-4", "color": "red", "width": 480 }], "ttl_seconds": 604800, "renew": true }
```
- The URL is created and charged like any other. When it is loaded after `expires_at`, the first load in each new window of `ttl_seconds` bills **one more credit** and renews it; every further load in that window is free and cacheable, exactly like the first window. Windows nobody opens cost nothing.
- With the 7-day maximum TTL the ceiling is one credit per image per opened week, about five cents for an image opened every week for a year.
- Billing is once per window whatever the open count: the ledger reference `renew:<id>:<window>` is unique, so two mail proxies racing on the first open pay once.
- An account that cannot pay a renewal gets `402` on that load and the image shows as broken until credits land; the URL itself stays valid and resumes on its next load. Suggest auto-reload for production accounts.
- Revoking the key still ends the URL (`403`), renewals included.
- `renew` cannot be combined with a use cap (`max_uses` must be 0).
Store `renews_until` next to the URL. The story at https://carimage.dev/customers/car-images-in-email?ref=plugin shows the exact email HTML.
## Patterns
### Next.js — server component
```tsx
import { CarImageClient } from "@meterapp/car-image-sdk";
const client = new CarImageClient(); // server only: the key stays in the deployment's secrets (car-image-sdk)
export default async function Hero() {
const { data } = await client.createImageUrls(
[{ make: "Porsche", model: "911", year: 2024, view: "side", color: "red", width: 768, height: 432, trim: true, format: "auto" }],
{ ttlSeconds: 86_400, idempotencyKey: "hero-porsche-911-2024-side-red" }
);
return <img src={data[0].url} alt="2024 Porsche 911, side view, red" width={768} height={432} />;
}
```
`width` and `height` together return exactly 768×432, so the `<img>` attributes match the bytes; `trim` makes the car fill that wide box instead of floating in a square. Wrap it in your data cache (`unstable_cache`, ISR, or your own table) keyed by the vehicle parameters so a page view does not mint a new URL — the idempotency key stops a retried build from paying twice, not a hot path from minting on every request.
### A batch for a comparison grid
```ts
const vehicles = [
{ make: "Toyota", model: "RAV4", year: 2023 },
{ make: "Honda", model: "CR-V", year: 2023 },
{ make: "Mazda", model: "CX-5", year: 2023 },
];
const { data } = await client.createImageUrls(
vehicles.map((v) => ({ ...v, view: "front-3-4", color: "silver", width: 512 })),
{ ttlSeconds: 604_800 }
);
```
One call, three credits. Results come back in request order.
### An inventory grid from a list of VINs
A dealer page, a marketplace import, a fleet dashboard: the input is a spreadsheet of VINs or of year, make and model, and most rows are the same few vehicles.
1. **Look up every row for free.** `decode_vin` for a VIN (`vehicle.id`), `resolve_vehicle` for a name (`params.vehicle_id`). Set aside the rows that do not resolve and report them; do not guess.
2. **Deduplicate by vehicle id.** Forty Camrys of one model year are one image. The cost is distinct vehicles × views × paints, not rows.
3. **Say the count and the cost, and get a yes.** "212 rows, 37 distinct vehicles, one view each: 37 credits."
4. **Mint in batches of 50** with an `idempotency_key` per batch, so a retried import does not pay twice.
5. **Store `url`, `expires_at` and the `vehicle_id` next to each row**, and re-mint by id before expiry. A page nobody rebuilds wants `renew: true`.
Show the decoded trim, body and engine as text beside the image; the render represents the model, not that car on the lot.
### One fixed image: a hero, a card, a slide
When the image never changes, a signed URL that expires is more machinery than the job needs. Download the file once and serve it from the project's own assets:
```bash
npx @meterapp/car-image get --vehicle veh_78qtwrgh37bkr --view front-3-4 --color red --width 1024 --height 576 --trim --padding 6 --format webp --out public/hero-911.webp
```
One credit, no expiry to track, no request at page load. Keeping copies on your own servers or CDN to serve your own product is part of a paid plan's license, for as long as the plan is active; the Free plan is for evaluation and personal projects, and a standalone library or bulk export needs a written agreement. `search_help` has the terms when the user asks (the `car-image-support` skill). Use signed URLs when the set of vehicles is open-ended: a catalog, a search result, a listing feed.
### Markdown and email
```md

```
Transparent PNG sits on any background, which is what you want in a document. For email, ask for a solid `background` (`"white"`, or the hex of the surrounding table cell) — it flattens PNG and WebP too, and `jpg` defaults to white — because some older mail clients render PNG transparency as black.
## Getting the image right
- **Transparent by default.** `format: "png"` has a real alpha channel, so the render drops onto any background without a box around it. Choose `webp` for smaller files with transparency, `jpg` only when the surface cannot do alpha, and `auto` for an `<img>` any browser will load: the URL negotiates WebP or PNG from each client's `Accept` header on every load.
- **Ask for the box you will display**, up to 1024 on each side. With both `width` and `height` the image comes back exactly that size (`fit: "contain"`, the default, keeps the whole car and pads the rest; `"cover"` fills the box and crops). For a non-square slot add `trim: true` so the car fills it instead of floating in the square source frame, with `padding` (0–50 %) for breathing room. A 512 px render in a 256 px slot wastes bytes; a 256 px render in a 512 px slot looks soft, and nothing is upscaled past 1024. For retina, request 2× the CSS size.
- **Set `width` and `height` on the `<img>`** so the page does not reflow while it loads — the values you asked for, since that is exactly what comes back.
- **Alt text describes the vehicle**: "2024 Porsche 911, side view, red". Not "car", not "image".
- **Cache publicly.** Delivery URLs are safe to put behind a CDN for their TTL; that is the point of them.
## Expiry is a real state
`expires_at` comes back with every URL. Whatever stores the URL must store that too.
- Re-mint before expiry, not after a user reports a broken image.
- A `403` on a delivery URL means expired, invalid, the key that created it was revoked, or `code: "origin_not_allowed"`: the page loading it is on a site the issuing key's allowed origins (set on the key in the dashboard) do not list; loads without a `Referer` or `Origin`, such as email clients, pass. A `410` means a use-capped URL hit its cap. Both are fixed by minting a fresh URL, not by retrying the old one. A `402` means a renewable URL entered a new window the account could not pay for; add credits and the same URL resumes.
- In chat, follow the host-specific display rules above and state expiry: ChatGPT/Codex gets a clickable link and browser opening when available; Claude keeps inline Markdown. Reuse the same URL without another paid call.
## Do not
- Mint a URL for a vehicle you have not looked up. One `404` fails the whole batch before anything is charged; it carries `suggestions` (and, over REST, the `index` of the entry), so fix that entry and send the batch again.
- Put an API key in client-side code, an `<img src>`, or a committed config file. Mint a signed URL instead.
- Mint a URL per request or per render without caching — each one is a credit.
- Treat a signed URL as private. It is unguessable, not access-controlled.
- Present a render as a photograph of one specific listed vehicle. It represents the model.
Referenced files: 1 — file references are metadata, not package contents.
vehicle-catalog17.6 KB
---
name: vehicle-catalog
description: Look a vehicle up before it is rendered - turn what someone asked for ("a 2018 Miata", "the new 911 GT3", a VIN) into the exact catalog make, model and year the Car Image API can render and the stable vehicle id (veh_…) that names it, or check what a make offers and which years exist, or which of a whole list of vehicles the catalog carries. Covers resolve_vehicle, search_vehicles, check_vehicles for lists and free VIN decoding (full or partial VINs), reading confidence and year mismatches, trims and body styles, picking a car when none was named, missing vehicles, and the open @meterapp/vehicle-db package for offline lookups, dropdowns and validation. Use first, before any image, signed URL or 3D model of a vehicle, and when building a vehicle picker or validating user input; do not use for fetching the image itself (car-image), embedding it in a page (car-image-urls) or making a 3D model (car-3d).
---
# Finding the right vehicle
Renders are addressed by **make, model, year** — or by the one **vehicle id** that stands for the three. The lookup comes first, every time: the catalog files cars under its own names, a render of the wrong car still costs a credit, and a 3D model of the wrong car costs a hundred.
The catalog is the open [`@meterapp/vehicle-db`](https://github.com/MeterApp/vehicle-db) — 1,600 makes and 41,975 models across model years 1990-2027, built from public sources (NHTSA vPIC, UK DfT/DVLA, NZTA, Malaysia JPJ and others). It covers cars, motorcycles, trucks, buses, MPVs and more, internationally — not just the US market. Every make, model and model year also has a public page under the same slugs, from [carimage.dev/cars](https://carimage.dev/cars?ref=plugin) down to `/cars/<make>/<model>/<year>`: link it when a person wants to browse what the catalog holds.
All the lookups below are **free**. Use them liberally.
`resolve_vehicle` and `search_vehicles` also work before anyone signs in: the plugin's `car-image-catalog` server serves them with no account, together with `preview_car_image`, the picture a vehicle's page already shows (the `car-image` skill, "Before the account is connected"). `decode_vin` and `check_vehicles` are on the account server, like every render.
## Look up, then hand over the id
| You have | Call | Take |
| --- | --- | --- |
| A car named in words ("2018 Mazda MX-5 Miata") | `resolve_vehicle({ query })` | `params.vehicle_id`, plus `params.view` and `params.color` |
| A VIN, full or partial | `decode_vin({ vin, year? })` | `vehicle.id` |
| A make, and a question about what it offers | `search_vehicles({ query, year?, limit? })` | `id` (the year the query names) or `ids` by year |
| Nothing named ("a random car", "something sporty") | choose a year, make and model yourself, then `resolve_vehicle`; name your pick in the reply | as above |
| A list of vehicles (a catalog, a spreadsheet, a feed) | `check_vehicles({ vehicles: [{ make, model, year, ref? }] })`, up to 100 a call; `car-image check <file>` or `client.checkVehicles` for more | `results[].vehicle.id` of each match |
Then render with the id: `create_car_image_urls` with the id, view and color in its `images` array for chat (paste the returned Markdown inline), a page or document (`car-image-urls`); `get_car_image({ vehicle, view, color })` for explicit files or bytes, `create_3d_model` for a model (`car-3d`). One lookup serves every view, paint and size of that car.
## Words in, a vehicle out
`resolve_vehicle` (REST: `POST /api/v1/images/resolve`) takes a phrase and returns `params` (with `vehicle_id`), `display`, `candidates`, `confidence` (`high`, `medium` or `low`) and `extracted`, what it read out of the phrase.
```
"red 2018 Mazda MX-5 Miata, side view"
→ params: { vehicle_id: "veh_59854qbgfvar3", make: "mazda", model: "mx-5", year: 2018, view: "side", color: "red" }
confidence: "high", extracted: { year: 2018, color: "red", view: "side" }
```
It is deterministic, with no model behind it, so write the phrase it can read:
- **Year, make, model.** The make first, then the model, is read by the rules the image endpoints read them with, so the answer is the vehicle a render would serve. Give the make whenever you know it: "mustang" alone is a guess among every make's Mustang, "ford mustang" is not.
- **A view and a paint, if the user named them.** "side", "from behind", "front 3/4", "three-quarter view", "rear three quarter right"; a preset, a hex such as `#1a2b3c`, or a paint word (navy, charcoal, cream) that maps onto a preset. You do not need to parse these yourself.
- **Nothing else.** A description is not a name. "That blue BMW wagon", "a mid-2000s Civic", "the electric one", "the new shape" do not resolve: turn them into a year, make and model first, and when you cannot tell which car is meant, ask. "A mid-2000s Civic" becomes "2005 Honda Civic", offered to the user, not picked silently.
- **Put the model year in.** Without one the answer is the model's latest catalog year, which can be next year's car. When the user gave no year, choose one and say which.
**Act on what comes back**, in this order:
- **`params.year` differs from `extracted.year`** — the catalog has no model of that name in the year the user asked for, and the answer is another year of it. Do not render it. First check whether the car is filed under another name that year: `search_vehicles` with the make, the model family and `year` ("Mazda MX-5", 2018). If that finds it, use that id and say which catalog model it is; if not, say the catalog lacks that model year, offer the year it has, and ask.
- **`low`** — a guess. Show the candidates and ask. Do not render.
- **`medium`** — an interpretation: a badge read as its family, trim words set aside, a body the catalog has no separate model for ("Civic hatchback" answered with the Civic), or a model name several makes use with no make given. Render, and say in one line which vehicle it is.
- **`high`** — the name the catalog uses, or a reviewed name for the same car (a 2018 "MX-5 Miata" is `mx-5`, a "GR Supra" is `supra`), in the year asked for. Render.
- **No match** — not yet a missing vehicle. Drop everything but the year, make and model and try again, or call `search_vehicles` with the model family alone. When the catalog really lacks it, say so plainly and stop. Do not substitute a similar car.
`candidates` lists what else the phrase could be: for a vehicle named with its make, the nameplate's other variants that year (the 911 Carrera and GT3 next to the 911). Offer them when the user may have meant one ("Civic" covers a dozen bodies across four decades), and never treat them as a reason to stall a `high` match.
## Vehicle ids
Every make, model and year in the catalog has an id such as `veh_395yw8tn73ff8` (the 2023 Ford F-150): `veh_` plus 13 characters, deterministic and permanent. `search_vehicles`, `resolve_vehicle`, `decode_vin` and `check_vehicles` all return them, and every image endpoint, signed-URL item, 3D request and MCP tool accepts `vehicle: "veh_…"` in place of `make`, `model` and `year` (both together is a `400`; an unknown id is a `404`). Every echoed vehicle object opens with `vehicle_id`.
Always render by the id: it cannot be misspelled, it survives catalog releases, and it is what to store in the user's database. `GET /api/v1/vehicles/{id}` (public, free) turns an id back into its make, model, year, every year of the model, and ready-made image paths.
## A whole list: check it once
A catalog, an inventory export, a dealer feed: check the whole list once, before rendering any of it, instead of rendering every vehicle in every view and color and collecting the `404`s. `check_vehicles` (REST: `POST /api/v1/vehicles/check`) reads each entry exactly as `get_car_image` would read it and answers each one, in order. Free.
```
check_vehicles({ vehicles: [
{ make: "Mazda", model: "MX-5 Miata", year: 2018, ref: "sku-1041" },
{ make: "Lexus", model: "TX 350", year: 2022, ref: "sku-1042" },
{ make: "Chevrolet", model: "Sail", year: 2022, ref: "sku-1043" } ] })
→ summary: { total: 3, match: 1, suggestion: 1, miss: 1, invalid: 0, distinct_vehicles: 1 }
results: [ { ref: "sku-1041", status: "match", vehicle: { id: "veh_59854qbgfvar3", model: "mx-5", match: "alias", … } },
{ ref: "sku-1042", status: "suggestion", reason: "year", suggestions: [ { model: "tx-350", year: 2024, id: "veh_8bbz514ey4ran" }, … ] },
{ ref: "sku-1043", status: "miss", reason: "model", search: "/api/v1/vehicles?q=chevrolet%20sail" } ]
```
- Send names as the data spells them; do not clean them up first. The check applies the image endpoints' own rules (nicknames, separators, aliases, trim words, badges, typos), and a name you rewrite may read differently from the one the data will send later.
- **`match`**: store `vehicle.id` beside the row and render by it from then on, in every view and color. `vehicle.match` other than `exact` means a looser rule read the name; for `trim`, `badge` and `fuzzy`, tell the user which vehicle it is.
- **`suggestion`**: the catalog lacks the vehicle as named. `suggestions` are other vehicles (the same model in its nearest year, a related model), never a stand-in: ask the user, or apply a rule they chose ("use the nearest year"), before storing one.
- **`miss`**: nothing close. Skip it in every view and color and say so; never fill the gap with another vehicle.
- **`invalid`**: `error` says what to fix (usually a missing year).
`summary.distinct_vehicles` is what rendering the matches counts against the plan's distinct vehicles a month (Free 100, Pro 2,500, Business 15,000): compare before a backfill. One call takes 100 entries; for a longer list write code instead of looping the tool: `car-image check inventory.csv --out inventory-checked.csv` writes the file back with `status`, `vehicle_id` and `suggested_vehicle_id` per row, and the SDK's `client.checkVehicles(rows)` splits any length (2,000 per request, 10 requests a minute). Check again only the misses and suggestions, after a catalog release: matches do not change.
## A VIN in, the vehicle out
`decode_vin` (REST: `GET /api/v1/vin/{vin}?year=`) decodes a full 17-character VIN, or a partial one with `*` for each unknown position (5–17 characters), with NHTSA's vPIC data, and names the catalog vehicle it maps to. Free.
```
"1HGCM82633A004352"
→ year: 2003, make: "Honda", model: "Accord", trim: "EX-V6", body_class: "Coupe", valid: true
vehicle: { id: "veh_3qfyk22gfhsx3", make: "honda", model: "accord", year: 2003, image_path: "/api/v1/images/car?vehicle=veh_3qfyk22gfhsx3" }
```
- Render with `vehicle: vehicle.id` (or `vehicle.image_path`); no spelling to get right.
- `valid: false` with `errors: [{code, text}]` means vPIC found a problem (a bad check digit, an invalid character) but still decoded what it could; `suggested_vin` is its corrected spelling when it can tell. Show the errors; still use the vehicle when there is one.
- `vehicle` is `null` when the decoded make and model are not in the catalog (a trailer, a commercial chassis). Say so; the decoded fields (`attributes` carries every vPIC variable) are still worth showing.
- The catalog keys on make, model and year: show the decoded `trim`, `body_class` and `engine` next to the image rather than claiming the render depicts them.
- Pass `year` when you know it: the tenth VIN character encodes the year in a 30-year cycle, and a hint helps a partial VIN.
- A `400` is not a VIN (length, an I, O or Q); a `404` is a pattern NHTSA does not know, so decoding it again will not help. Ask the user to check the characters rather than guessing, above all the first three (the maker code), or decode what they are sure of as a partial VIN with `*` and a `year`. NHTSA's data covers vehicles made for the US market: find any other car by name with `resolve_vehicle` or `search_vehicles`.
## Searching and listing
`search_vehicles` (REST: `GET /api/v1/vehicles?q=`) matches makes and models with typo tolerance and returns canonical names plus the years available for each, with a vehicle id per year (`ids`) and `id` when the query names a single year.
Every word of the query must be in the name of one catalog vehicle, so **fewer words find more**: "honda civic" lists the Civic and every Civic variant, "honda civic touring sedan" may list nothing. When no vehicle carries every word but the query names a make and a model the image endpoints would read ("Mazda MX-5 Miata" in 2018), the search answers with that one vehicle. An empty result is a reason to shorten the query or call `resolve_vehicle`, not yet a missing vehicle.
Use it to:
- **List a make's models** — search the make, read the models back.
- **See a model's variants** — "porsche 911" with `year: 2024` lists the 911, the Carrera, the GT3, the Targa and the rest, each with its id, for the user to pick from.
- **Find valid years** — the response tells you which model years exist, so you never request one that does not, and hands you the id of each.
- **Recover from a 404** — read its `suggestions` first (real catalog vehicles, closest first, with ids); search only when they are empty.
Render and catalog lookups also accept vehicle-db's reviewed aliases (`maccane` → `macan`, `prado` → `land-cruiser-prado`) after local matching misses. BMW petrol badges preserve an explicit body (`430-gran-coupe` → `430i-gran-coupe`). These require one catalog model in the requested make/year and report `X-Vehicle-Match: alias`; ambiguous or unverified results stay 404s. A search result's rank alone is not permission to substitute it.
The catalog's spelling wins, and the render endpoints read the common alternatives themselves (`/docs/vehicles#matching`): nicknames (`Mercedes`, `VW`, `Chevy`), other separators (`cx5`, `landrover`), word order and language (`Série 3`, `Classe A`, `q6-e-tron-sportback`), same-body names (`rx-350`, `camry-hybrid`, `740`, `mx-5-miata`), engine and trim words (`tucson-n-line-s-t-gdi-hev-auto`, `x3-xdrive30d`), badges (`c300`, `760`) and typos (`elentra`, `porshe`). The response header `X-Vehicle-Match` says whether the spelling was literal (`exact`) or which rule interpreted it (`prefix`, `separator`, `family`, `words`, `alias`, `trim`, `badge`, `fuzzy`); `vehicle_id` / `X-Vehicle-Id` names the vehicle served. Body styles, performance derivatives and electric twins are never substituted, and neither is a model year the catalog does not carry: those answer `404` with `suggestions`, real catalog vehicles with ids you can retry with (`vehicle: <id>`) or show to the human. When a render's `X-Vehicle-Match` is not `exact`, say in one line which vehicle was served. None of this is a reason to render by name: the lookup tells you what you will get before a credit is spent.
## Disambiguating like a human would
The catalog keys on make, model and year, and it is merged from several registries, so what counts as a "model" varies. Ask the lookup rather than assuming either way.
- **Many variants are models of their own.** Civic Si and Civic Type R, M3 Competition, F-150 Raptor, 911 GT3, Corolla Hatchback, A4 Avant, Cooper Convertible, Wrangler Rubicon: `resolve_vehicle` returns them as themselves, and that is what to render.
- **Others are not in the catalog.** A Civic hatchback in a year that has only the Civic, a Golf Estate, a Camaro SS: the lookup answers with the nameplate at `medium`, a neighbour at `low`, or nothing. Say which vehicle the image represents, and do not claim it shows the trim or the body that was asked for.
- **Options and packages are never a parameter.** Wheels, a sport package, an interior: render the model and say what it is.
- **Year means model year.** "A 2023 car bought in 2022" is a 2023. When someone says "a mid-2000s Civic", offer a specific year rather than picking one silently.
- **Generation talk needs a year.** "992 911", "E90 M3", "Mk7 Golf" — map it to a model year, and confirm if you are unsure.
- **Regional names differ.** The MX-5 is a Miata in North America; the same car can be a Corolla in one market and something else in another. Look it up rather than assuming.
## When the user did not name a car
"A random car", "surprise me", "a fun convertible", "a family SUV". Choose one yourself: a specific year, make and model you are confident exists, varied from one request to the next. Tell the user which you chose, then look it up and render it like any other. The lookup still comes first: the car you thought of is a name from memory.
## Offline: the npm package
For a vehicle picker, a validation rule, or anything that should not make a network call per keystroke, install the catalog directly:
```bash
npm install @meterapp/vehicle-db
```
It is open source, has no runtime dependencies, and ships the full make/model/year data. Use it to populate cascading dropdowns (make → model → year), validate a form before submitting, or pre-check that a vehicle exists before calling the API. Then pass the values you selected straight to the render call — they are already canonical. The package holds the catalog, not the API's reading rules: to learn what a render of names spelled your own way will do, check them with `check_vehicles`.
This is the right choice when you need instant filtering, offline behavior, or hundreds of lookups. Use `search_vehicles` when you want fuzzy matching and typo tolerance over free text.
## Telling the user what you did
When you resolved something that needed interpreting, say so in one line: "This is the 2022 Honda Civic — the catalog has no separate hatchback for that year." or "There is no 2020 WRX STI in the catalog; the newest is 2017. Want that one?" That is the difference between a user trusting the image and being surprised by it.
If the catalog does not have the vehicle, say that and stop, rather than rendering the nearest thing. A substituted car is worse than no image.
Referenced files: 1 — file references are metadata, not package contents.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package license
- MIT
- Package author
- Meter
- Keywords
- See keywords
Declared capabilities
- Read
- Write
Package observed Nov 3, 2026.
Technical details
- First seen
- Oct 29, 2026 · 18:00 UTC
- Last seen
- Nov 4, 2026 · 06:00 UTC
- Collection status
- Collected
plugin_asdk_app_6aa6d30bc5d88191a148b89fa75dd324
Download plugin data (JSON)