Car Image API
Documentation

Rate limits & caching

120 req/min per key, RateLimit-* headers, ETags and cache policy.

Rate limits

Two fixed 60-second windows apply to every keyed request. Each API key may make the requests per minute its plan allows (120 on Free and Pro, more on Business and Enterprise), and each account has one limit across every key and connected app it holds, so ten keys are not ten budgets. Every response announces the per-key budget so clients can pace themselves without guessing; an Enterprise agreement can give a key its own limit. Unauthenticated endpoints (device codes, the playground) are limited per IP.

PlanPer keyPer account, all keys
Free120 / min240 / min
Pro120 / min600 / min
Business600 / min2,000 / min
Enterprise1,200 / min5,000 / min
HeaderExampleMeaning
RateLimit-Limit120Requests allowed per window for this key.
RateLimit-Remaining117Requests left in the current window.
RateLimit-Reset42Seconds until the window resets.
Retry-After42Only on 429: integer seconds to wait before retrying.
429 Too Many Requests
HTTP/2 429
content-type: application/problem+json
ratelimit-limit: 120
ratelimit-remaining: 0
ratelimit-reset: 42
retry-after: 42

{"type":"https://carimage.dev/docs/errors#429","title":"Too Many Requests","status":429,"detail":"Rate limit exceeded for this key. Retry after 42 seconds.","request_id":"req_01j9x…"}

A 429 whose problem document carries "code": "account_rate_limited" is the account-wide window rather than this key's: the same Retry-After applies, the plan member names the plan, and a higher plan has a higher limit. See Errors.

ETags: free repeats

Every image response carries a stable ETag per variant. Send it back as If-None-Match and an unchanged image returns 304 Not Modified with no body and no credit charged. Weak comparison is used, so a W/ prefix on either side still matches.

bash
curl -sI -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  -H 'If-None-Match: "9f2a…c41d"' \
  "https://carimage.dev/api/v1/images/car?make=porsche&model=911&year=2024&view=side"
# HTTP/2 304
# etag: "9f2a…c41d"

Cache policy by response type

ResponseCache-ControlWhy
Keyed image bytes (GET /api/v1/images/car)private, no-storeBilled requests reach authorization and metering. Save bytes explicitly and use ETags for free conditional requests, or use signed URLs for embeds.
Signed URL, unlimited usespublic, max-age=<up to 300>, s-maxage=<up to 3600>, must-revalidateBilled once at creation. Browser caching is capped at five minutes and shared caching at one hour, always within the URL lifetime. Reuse the same URL to maximize CDN hits.
Signed URL with max_usesprivate, no-storeEvery redemption must reach the origin so the use counter can be enforced.
Free example imagespublic, max-age=300, s-maxage=3600, stale-while-revalidate=86400Free and public, so browsers keep them for five minutes and the edge for an hour. After that the edge serves the copy it holds for up to a day while it refetches in the background, so a visitor never waits for the refresh.

The rule of thumb: a response that cost a credit is private to the caller; a response that was prepaid (a signed URL) or free (an example) may be shared. If you want a CDN in front of your images, mint signed URLs with a long TTL and unlimited uses.

Cold renders and concurrency

Cold renders are the slow path (about 20 seconds). Concurrent requests for the same new variant share one generation; each delivered image costs one credit. Allow three minutes on the client for generation and coordination. Spread large batches out: generation capacity and the daily provider budget are finite, separate from rate limits.

Render capacity per account

Each account may spend a share of the day's render budget on cold renders: 2% on Free, 10% on Pro, 25% on Business, 50% on Enterprise. Once an account has used its share, a request that would need a new render answers 429 with "code": "account_generation_cap", retry_after_seconds and reset_at. The share resets at midnight UTC; images already rendered keep serving from the cache, so a request for a cached image is never refused this way, and retrying before reset_at cannot succeed. Nothing is charged for the refusal. A higher plan has a larger share, and Enterprise can reserve capacity.