Car Image API
Documentation

Authentication

API keys, scopes, the device flow and where keys must never go.

Bearer keys

Every API request carries a key that starts with cimg_. Send it in the Authorization header. The x-api-key header is accepted for compatibility, but Authorization: Bearer is preferred.

bash
curl -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  https://carimage.dev/api/v1/account

Creating and revoking keys

Keys are created in the dashboard or by the CLI. The plaintext is shown exactly once; the server stores only a SHA-256 hash plus a short prefix for display. You can hold up to 10 active keys per account — one per environment or agent is a good habit, because revocation is instant and per-key.

  • Dashboard keys get images:read and account:read.
  • CLI keys (approved in the browser at /activate) also get billing:write, so an agent can hand you a Stripe Checkout link when credits run low.

Scopes

API key scopes
ParameterTypeDescription
images:read
scope
Fetch images, create signed URLs, search and resolve vehicles, send feedback.
account:read
scope
Read the credit balance and pricing from GET /api/v1/account.
billing:write
scope
Create hosted Stripe Checkout URLs to buy credits. Never completes a charge — a human still pays in the browser. Granted to keys from the CLI device flow, and to a connected app only when the consent screen listed it. The Billing Portal (invoices, saved cards) needs a key or the dashboard: its URL is itself a credential, so connected apps cannot obtain one.
billing:purchase
scope
Pay for a plan or an open plan invoice with a payment token (a Stripe Shared Payment Token), only within the monthly agent spending limit the owner set in the dashboard. Off by default and never granted by the CLI device flow or to a connected app: the owner turns it on per key in the dashboard's Let agents buy card. A key with allowed origins cannot hold it.

A request outside a key's scopes fails with 403; the response body names the scope that is missing.

Device flow (CLI and agents)

npx @meterapp/car-image login requests a device code, prints a short user code and opens https://carimage.dev/activate. You sign in and approve the code; the new key is delivered only to the waiting CLI, stored at ~/.config/car-image-api/config.json with mode 0600, and never shown in the browser.

POST/api/auth/device/code
nonefree
POST/api/auth/device/token
device codefree

Codes expire after ten minutes. Polling before approval returns authorization_pending; the key is returned exactly once, after which the code is consumed.

OAuth for connectors

A chat client cannot be handed a key, so the MCP endpoint speaks the MCP authorization spec: OAuth 2.1 with Supabase Auth as the authorization server and carimage.dev as the protected resource and consent screen. Point a connector at https://carimage.dev/api/mcp and the client discovers everything else. Sign-in is the same account you use here.

  • Authorization code with PKCE and dynamic client registration (RFC 7591) — no client secrets, nothing configured by hand.
  • Discovery starts at /.well-known/oauth-protected-resource (RFC 9728), named by the WWW-Authenticate header on any unauthenticated request; it points at the authorization server's own metadata.
  • You approve at /oauth/consent. The screen names the app, the account, your balance and exactly what the app will be able to do.
  • A connected app holds images:read and account:read: it can render images and read your balance, and can never buy credits or open billing.
GET/.well-known/oauth-protected-resource
nonefree
GET/oauth/consent
browser sign-infree

Checking your balance

GET/api/v1/account
account:readfree

The balance and the prices it buys, abridged. The full response also carries the plan in force as data.plan (see Pricing & billing), the last 30 days as data.usage_30d, the calling key as data.key and every plan as data.pricing.plans.

200 OK
{
  "data": {
    "credits": 4871,
    "credits_value_usd": 48.71,
    "auto_reload": false,
    "has_payment_method": true,
    "pricing": {
      "credits_per_image": 1,
      "credits_per_dollar": 100,
      "packs": [
        {
          "credits": 100,
          "cents": 100,
          "label": "$1 — 100 credits"
        },
        {
          "credits": 500,
          "cents": 500,
          "label": "$5 — 500 credits"
        },
        {
          "credits": 1000,
          "cents": 1000,
          "label": "$10 — 1,000 credits"
        },
        {
          "credits": 2500,
          "cents": 2500,
          "label": "$25 — 2,500 credits"
        },
        {
          "credits": 10000,
          "cents": 10000,
          "label": "$100 — 10,000 credits"
        }
      ]
    }
  },
  "links": {
    "create_checkout": "/api/v1/billing/checkout",
    "billing_portal": "/api/v1/billing/portal",
    "pricing": "/pricing",
    "docs": "/docs",
    "openapi": "/openapi.json",
    "mcp": "/api/mcp"
  },
  "request_id": "req_01j9x…"
}

Environment variables

The CLI, the SDK and the local MCP server all read the same two variables:

ParameterTypeDescription
CAR_IMAGE_API_KEYrequired
string
Your cimg_ key. Inject it as a secret in CI or an agent sandbox.
CAR_IMAGE_API_URL
url
Override the API origin (self-hosting, staging).Default: https://carimage.dev
Never commit a keyNever paste a key into a promptRotate by creating a new key, then revoking the old one