# Car Image API for agents

Studio-quality, transparent-background renders of any vehicle in the open [`@meterapp/vehicle-db`](https://github.com/MeterApp/vehicle-db) catalog (1,145 makes, 14,841 models, 1990–2026): 6 views, 15 colors, PNG/WebP/JPG up to 1024px. **1 credit per image, $1 = 1,000 credits, 100 free credits, no subscription.** Base URL `https://car-imgs.vercel.app`. Full spec: [`/openapi.json`](https://car-imgs.vercel.app/openapi.json). Everything an LLM needs in one file: [`/llms-full.txt`](https://car-imgs.vercel.app/llms-full.txt).

## Get a key

```bash
npx @meterapp/car-image login      # opens the browser; a human approves a code
export CAR_IMAGE_API_KEY=cimg_...  # or copy a key from https://car-imgs.vercel.app/dashboard
```

The CLI login is an RFC 8628 device flow: `POST /api/auth/device/code` -> the human visits `verification_uri_complete` -> the CLI polls `POST /api/auth/device/token` until it receives `{access_token}` **once**. Keys start with `cimg_`. Dashboard keys have `images:read account:read`; CLI keys also get `billing:write` (which only creates hosted Stripe pages, never a charge). Keys live in env vars or secret stores — never in URLs, prompts, tool results, MCP config files or logs.

## Three ways in

1. **MCP (remote):** `https://car-imgs.vercel.app/api/mcp` with header `Authorization: Bearer $CAR_IMAGE_API_KEY` (Streamable HTTP). Local stdio alternative: `npx @meterapp/car-image mcp`. Tools mirror the endpoints below.
2. **CLI:** `npx @meterapp/car-image get --make porsche --model 911 --year 2024 --view side --color red --out car.png`.
3. **REST** (below). TypeScript SDK: `npm i @meterapp/car-image-sdk`.

## Endpoints

| Method & path | Auth | Credits | Purpose |
| --- | --- | --- | --- |
| `GET /api/v1/images/car?make&model&year[&view&color&size&w&h&format]` | key `images:read` | 1 | Image bytes. `If-None-Match` -> free 304. `?response=url` -> signed URL as JSON (same charge). |
| `POST /api/v1/image-urls` | key `images:read` | 1 per URL, at creation | 1–50 browser-safe URLs, free to load until `expires_at`. |
| `GET /api/v1/delivery/{token}` | none | 0 | Loads a signed URL (renders on first load, cached after). `HEAD` validates. |
| `POST /api/v1/images/resolve` `{query}` | key `account:read` | 0 | "red 2024 porsche 911 side profile" -> exact params + `image_path` + confidence. |
| `GET /api/v1/images/options` | none | 0 | Views, colors, sizes, formats, pricing, limits, catalog stats. |
| `GET /api/v1/vehicles[?q\|?year[&makeId]\|?make[&model]]` | none | 0 | Catalog browse and search; returns slugs. |
| `GET /api/v1/account` | key `account:read` | 0 | Balance, 30-day usage, key scopes and rate limit. |
| `POST /api/v1/feedback` | key `account:read` | 0 | Rate an image by `request_id` or by vehicle. |
| `POST /api/v1/billing/checkout` `{credits}` | key `billing:write` | 0 | Hosted Stripe Checkout URL (5,000 / 10,000 / 25,000 / 100,000 credits). |
| `POST /api/v1/billing/portal` | key `billing:write` | 0 | Stripe Customer Portal URL. |
| `GET /images/car.png?brand&model&year&view…` | key, signed `exp`+`sig`, `mode=cached`, playground | 1 with a key, else 0 | Legacy embed endpoint. |

Parameters: `make` (or `brand`) and `model` accept names or slugs (`Mercedes-Benz` = `mercedes-benz`, `Model 3` = `model-3`; a unique model prefix works); `year` 1990–2027; `view` `front | front-3-4 (default) | side | side-right | rear | rear-3-4`; `color` `white black gray silver(default) blue red green brown beige tan orange yellow gold burgundy purple`; `size` `thumb=256 small=512 medium=768 large=1024` or `w`/`h` ≤ 1024; `format` `png (default, transparent) | webp (transparent) | jpg`.

### Examples

```bash
# Resolve a description first (free), then fetch exactly that (1 credit)
curl -s https://car-imgs.vercel.app/api/v1/images/resolve \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" -H "Content-Type: application/json" \
  -d '{"query":"navy 2019 jeep wrangler front 3/4"}'
# -> data.image_path = "/api/v1/images/car?make=jeep&model=wrangler&year=2019&view=front-3-4&color=blue"

curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  "https://car-imgs.vercel.app/api/v1/images/car?make=jeep&model=wrangler&year=2019&view=front-3-4&color=blue&size=medium&format=webp" \
  -D headers.txt -o wrangler.webp
# headers: X-Credits-Charged: 1, X-Credits-Remaining, X-Image-Source: cache|generated, ETag, Content-Location

# Browser embeds: mint URLs server-side (1 credit each now), load them for free for a day
curl -s -X POST https://car-imgs.vercel.app/api/v1/image-urls \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" -H "Content-Type: application/json" \
  -d '{"images":[{"make":"BMW","model":"M3","year":2022,"view":"side"}],"ttl_seconds":86400,"max_uses":0}'
# -> data[0].url = https://car-imgs.vercel.app/api/v1/delivery/<token>

curl -s -H "Authorization: Bearer $CAR_IMAGE_API_KEY" https://car-imgs.vercel.app/api/v1/account
```

## Billing semantics (exact)

- A delivered image (`200` from `/api/v1/images/car`, or a keyed `/images/car.png`) costs 1 credit, cached or generated. `X-Image-Source` tells you which; the price is identical.
- `304 Not Modified` is free. Always send `If-None-Match` when re-requesting an image you have.
- Signed URLs cost 1 credit each **when created**; loading them (`/api/v1/delivery/{token}`) is free, any number of times, until `expires_at` (default 1 h, max 7 d). `max_uses: 0` (default) = unlimited and publicly cacheable; a positive cap disables shared caching and returns `410` when exhausted.
- `?response=url` on `/api/v1/images/car` is the one-image version of the same deal (1 credit now, free loads later).
- Failures are refunded: a `502` after a charge, or a signed URL whose first render fails, returns the credit automatically. Nothing else ever costs credits.
- Rate limit: 120 requests/minute per key (`RateLimit-*` headers; `Retry-After` on 429).

## Safety rules

- Never put an API key in a URL, prompt, log, tool result or MCP config file. Browsers get signed URLs.
- On `402`, stop and tell the human the `balance` and `required_credits`. Do not open or complete Stripe Checkout without an explicit request; never enter payment details.
- On `429`/`503`, wait `Retry-After` seconds and back off with jitter. Do not retry `400`/`404` unchanged — fix the parameters (use `/api/v1/images/resolve` or `/api/v1/vehicles?q=`).
- Prefer `size` presets and `webp` for web use; request `png` only when you need transparency at full size.
- Use `POST /api/v1/feedback` (`verdict: "bad"`, `reason`) when a render is wrong; two negative reports pull it for regeneration.
- These are generated product renders, not OEM photography. Say so when accuracy matters, and never claim a specific trim or year is depicted exactly.
- Keep `request_id` (also in the `X-Request-Id` header) for support and for feedback.

## Embedding without the API: signed `/images/car.png`

Partners with an `IMAGE_URL_SIGNING_SECRET` can embed images directly, free and publicly cacheable, with no key in the page: add `exp` (unix seconds, at most 30 days ahead) and `sig` = lowercase hex `HMAC-SHA256(secret, "/images/car.png?" + query)` where the query is every parameter except `sig`, sorted by key and serialized like `URLSearchParams`. Signed requests are never redirected, so sign the exact spelling you will embed. Without a secret, `mode=cached` serves any variant that already exists in the cache, publicly and for free, and never triggers generation (`404` otherwise).

More: [`/errors.md`](https://car-imgs.vercel.app/errors.md) · [`/docs`](https://car-imgs.vercel.app/docs) · [`/pricing`](https://car-imgs.vercel.app/pricing) · [catalog](https://github.com/MeterApp/vehicle-db) · hello@meterapp.co
