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, no-store | Billed requests reach authorization and metering. Save bytes explicitly and use ETags for free conditional requests, or use signed URLs for embeds. |
| Signed URL, unlimited uses | public, max-age=<up to 300>, s-maxage=<up to 3600>, must-revalidate | Billed 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_uses | private, no-store | Every redemption must reach the origin so the use counter can be enforced. |
| Free example images | public, max-age=300, s-maxage=3600, stale-while-revalidate=60 | Public images stay fast at the edge while upgrades become visible on revalidation. The original image stays available during background upgrades. |
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 (up to two minutes). 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.