Errors and limits
Every status code the API returns, what your plan allows per minute, and the headers that tell you where you stand.
Keys
Send the key as a header, or as ?key= where a header is impossible.
curl https://pixloom.dev/api/v1/styles -H "Authorization: Bearer sk_live_..."
curl "https://pixloom.dev/api/v1/styles?key=sk_live_..."Secret keys (sk_live_) reach everything. Publishable keys (pk_live_) reach only what a browser needs: job, styles, embed resolve, analytics and the image proxy. Use one anywhere else and you get:
403 { "error": "Publishable keys cannot access this endpoint. Use a secret key (sk_live_)." }Account status
The beta is invite-only and every account is approved by hand. Until yours is, every call returns:
403 { "error": "Account pending approval", "code": "pending_approval" }pending_approval is the one error code worth branching on. Retrying does not help; wait for the email. A suspended or banned account gets a 403 too, with Account suspended or Account banned and no code.
Domains
Each key carries a list of domains it may run on. We check the browser's Origin header, or Referer for the image proxy, since browsers do not send Origin for image loads.
- An empty list means any domain. That is what you want for a server-side secret key.
*.example.comcoversexample.comand every subdomain.- Localhost always passes, so development needs no setup.
- A request with no origin at all passes, which is every server-to-server call.
A blocked origin gets 403 { "error": "Origin not allowed" }.
Rate limits
Per minute, counted against the key, on a sliding window.
| Plan | Requests per minute |
|---|---|
| Free | 30 |
| Hobby | 60 |
| Pro | 120 |
| Business | 300 |
| Enterprise | 600 |
The limit applies to generate, render and embed resolve. Analytics has the same ceiling in a separate bucket, so telemetry never spends your generation budget. Polling a job, listing styles and listing characters are not limited at all.
Responses carry X-RateLimit-Limit and X-RateLimit-Remaining. Over the line:
429 { "error": "Rate limit exceeded", "retryAfterMs": 60000 }Back off for retryAfterMs and try again. The window slides, so a steady pace recovers faster than a burst.
Generations
Your plan includes a number of generations a month. The counter resets on a rolling monthly date, not the first of the month.
Only fresh images count. Cache hits, instant mode, permalinks serving something they already have, and everything in the library are all free.
Every generation response carries where you stand:
X-Quota-Used: 42
X-Quota-Limit: 100
X-Quota-Reset: 2026-10-01T00:00:00.000ZOut of generations:
402 {
"error": "Generation quota exceeded",
"used": 100,
"limit": 100,
"resetAt": "2026-10-01T00:00:00.000Z"
}Render tries harder before giving up: if you already made an image from that exact prompt, it redirects there instead of failing. A page that has been live for a while keeps working after you hit your limit.
Status codes
| Status | Meaning |
|---|---|
200 | Done. |
202 | Accepted, work continues in the background. Generate and analytics. |
302 | Redirect to an image. Render, image, proxy. |
400 | Bad input. { "error": "Invalid request", "details": { ... } }, with details naming the fields. |
401 | Missing API key or Invalid API key. |
402 | Out of generations. |
403 | Account not approved, wrong key type, or a domain that is not on the key. |
404 | Not found, or not yours. We do not distinguish the two. |
429 | Over the rate limit. |
500 | { "error": "Internal server error" }. Ours, not yours. Retry, and tell us if it persists. |
502 | Generation failed at the model. Retry. |
Validation errors
A bad body comes back with the field that was wrong:
400 {
"error": "Invalid request",
"details": {
"formErrors": [],
"fieldErrors": { "width": ["Number must be a multiple of 64"] }
}
}A body that is not JSON at all gets { "error": "Invalid JSON" }.
CORS
Every /api/v1/ endpoint answers preflight and allows GET, POST and OPTIONS with Content-Type and Authorization. When your key has a domain list, we echo back the matching origin. When it does not, we allow any. We never reflect an origin we have not checked.