Rate limits & caching
120 req/min per key, RateLimit-* headers, ETags and cache policy.
Rate limits
Each API key may make 120 requests per minute by default, counted in a fixed 60-second window. Every response announces the budget so clients can pace themselves without guessing; higher per-key limits are available on request. Unauthenticated endpoints (device codes, the playground) are limited per IP.
| Header | Example | Meaning |
|---|---|---|
RateLimit-Limit | 120 | Requests allowed per window for this key. |
RateLimit-Remaining | 117 | Requests left in the current window. |
RateLimit-Reset | 42 | Seconds until the window resets. |
Retry-After | 42 | Only on 429: integer seconds to wait before retrying. |
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…"}Retry-After seconds plus a little random jitter, then retry. Batch work should watch RateLimit-Remaining and slow down before hitting zero. Rate-limited requests are never billed.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.
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
| Response | Cache-Control | Why |
|---|---|---|
| Keyed image bytes (GET /api/v1/images/car) | private, max-age=86400 | Your server or browser may keep the bytes for a day; shared caches must not, because the response was authorized by your key and metered. |
| Signed URL, unlimited uses | public, max-age=<remaining TTL>, s-maxage=<remaining TTL>, stale-while-revalidate=60 | Billed once at creation, so CDNs and browsers may serve repeats for free until the URL expires (capped at one day per cache entry). |
| Signed URL with max_uses | private, no-store | Every redemption must reach the origin so the use counter can be enforced. |
| Free example images | public, s-maxage=31536000, stale-while-revalidate=86400 | The allowlisted landing-page images are public and immutable. |
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.
Generation and concurrency
First renders are the slow path (about ten seconds). Concurrent requests for the same new variant are coalesced onto one render — the second caller waits for the first instead of paying for a duplicate. Very large batches of never-seen vehicles should be spread out; the rate limit, not the renderer, is the ceiling you will hit first.