A 429 is not automatically retryable. A rate-limit 429 carries X-RateLimit-* headers and a short Retry-After: it is a transient window, so wait that many seconds, then retry with a hard cap on attempts. A credit-cap 429 has no X-RateLimit-* headers, says the daily credit cap was reached, and its Retry-After is an hour or more: access is paused until you act — raise the cap, wait for the 00:00 UTC reset, or stop generating. Blind-retrying that response hammers a dead endpoint. The status code is the same. The recovery is not.
Two different 429s
HTTP 429 means "too many requests." That sentence hides two controls that look identical on the wire and behave differently in production.
A rate limit counts calls. Per minute, per day, concurrent in-flight. It exists so one key cannot occupy the fleet. When you trip it, the next call is refused for a short window. The window closes. The same call then succeeds without you changing a budget, a prompt, or a key.
A credit cap counts spend. PicX bills in credits per generation, never a currency amount on the meter. There is a daily credit ceiling, and there are hard spend caps you can set on a key. When you trip one of those, the next generate is refused because the budget for that key is gone, not because you were too chatty in the current window. Waiting a couple of seconds does not mint credits. Retrying does not raise the cap.
Those are different failures. Treating them as one retry policy is how a coding agent turns a paused key into a request storm. Do not hardcode the numbers; call GET /v1/account/tier with the same key and cache requests_per_minute, requests_per_day, concurrent_limit, and max_credits_per_day.
Rate limits protect the API. Spend caps protect you. A patient loop that honors a per-minute limit can still drain a balance. A cap on the key is the stop that rate limits are not. If you only build backoff, you have solved the wrong half of the problem.
Read the Retry-After header
Do not branch on status === 429 and sleep a default. Read the headers, then the body.
If Retry-After is present, the server is describing a window. Honor it. Do not retry immediately. Do not pick your own delay and hope. The value is seconds to wait before the next attempt. Rate-limit 429s also carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Official PicX Python and JavaScript SDKs (picx-ai on npm and PyPI) already honor Retry-After: they retry 429, 5xx, and network errors, and they never retry 400, 401, 403, 404, or 422.
If the 429 has no X-RateLimit-* headers and its body says the daily credit cap was reached, there is no request window to wait out, even though it still sends Retry-After (PicX sends 3600). A spend-cap pause that will not lift until a human changes the cap, or until a daily reset you are not going to sit on, belongs here. Queueing the same POST again is not recovery. It is load on an endpoint that will keep saying no.
A common snippet sleeps whatever Retry-After says, or a few seconds when it is missing, and continues the loop. That is the wrong default for a cap. Sleeping an hour inside a request handler is not recovery, and inventing a short wait converts a terminal refusal into a tight loop.
Even a present Retry-After is not a blank check. A daily credit cap can return 429 with a wait that stretches until 00:00 UTC. That header is honest. Sleeping until midnight inside a generate call, an MCP tool, or a CI step is not. Treat a long wait as a signal to surface, not as a sleep you should actually take on the request path.
The body detail field is worth logging. Humans read it. Agents can read it if you put it in the tool result instead of swallowing the status code. Do not parse English as your only branch. The header is the contract. The sentence is the explanation.
What to retry, what to surface
The decision rule is short. Apply it before any backoff helper.
Retry, with a bounded attempt count:
- 429 with a
Retry-Afterthat describes a short request window - 5xx
- network failures and timeouts on the client
Do not retry. Surface to the caller, the user, or the agent transcript:
- 429 without
X-RateLimit-*headers (the daily credit cap) - 429 whose
Retry-Afteris a daily or billing reset, not a request window - 401, 403, 404, 422, and other 4xx that are not 429
- a generate that already returned a hosted URL
A retry limit is part of the rule. A handful of attempts that honor Retry-After is recovery. A loop with no ceiling is a hammer. After the last attempt, raise. The SDKs surface RateLimitError only once their own retries are exhausted. If you catch that error, waiting further in the same request is usually the wrong place. Tell the user. Back off the whole job.
Idempotency belongs on the retryable path. A generate POST that timed out may have succeeded. Retrying without an idempotency key can bill a second generation. PicX image generation is synchronous: a simple generate call returns a hosted image URL in the response body. If you have the URL, you are done. Do not POST again to be sure. Video and batch are asynchronous and delivered by webhook. Persist the receipt. Do not re-submit because a webhook is late.
The trade-off is real. Fail closed on a brief rate-limit window and a burst looks like an outage. Retry a spend-cap 429 and you add latency, log noise, and pressure on an endpoint that will keep refusing. Prefer a paused state you can inspect over a loop you cannot see.
One REST API at https://api.picxstudio.com. One key with a pxsk_ prefix. Auth is a single header:
Authorization: Bearer $PICX_API_KEYRoughly 33 image and video models sit behind that key. The 429 you get from any of them is classified the same way. Do not special-case a model. Special-case the headers.
Why a coding agent will get this wrong
Agents retry. That is the job. A tool returns an error, the model assumes the call failed, and it calls again. A prompt says keep going until it looks right. A timeout on a long still looks like a miss, so the agent generates a second image. None of that requires malice.
The same pxsk_ key authenticates the REST API, the MCP server (19 tools) at https://picxstudio.com/developers/mcp, Agent Skills at https://picxstudio.com/skills, the CLI, and the official SDKs. A retry loop on any of those surfaces spends the same credits. A 429 from an MCP tool is not a different 429 from a 429 in a script. The agent does not hold a running total. It will not feel the cap. If the only instruction is "retry on error," it will.
Give the agent a classification, not a slogan. If generate returns 429 without X-RateLimit-* headers, stop and tell the user the key's credit cap is reached. If it carries them and Retry-After is short, wait once, retry once, then stop. That is executable. "Handle rate limits" is not.
Claude Desktop, Cursor, and ChatGPT will call tools on your behalf, including after a prompt you did not write. Set a hard spend cap on the agent key before you paste it. Connecting does not spend. Generation does. When the cap hits, the refusal is the feature. Do not teach the agent to raise the cap. Do not reuse the playground key for the agent. Mint a named key, cap it, and revoke it without touching production.
Hosted output removes a second retry loop you do not need. Generated files live on PicX's own CDN. You do not bring a bucket. You do not configure a second host. The URL in the body is the file. A pipeline that generates on one vendor and uploads to another has two 429s to misread and two meters to cap.
Bounded retries, then a human
Wire the rule once, at the HTTP client, not in every tool handler.
Use the official SDKs if you are writing application code. They already restrict retries to 429, 5xx, and network errors, honor Retry-After, and leave 422 and auth failures alone. Configure max_retries in Python or maxRetries in JavaScript. Set it to zero in a tight agent tool if you would rather see the raw 429 than wait inside the client. Catch RateLimitError and read retry_after (Python) or retryAfter (JavaScript). If that value is a request window, you may schedule the job later. If it is missing or points at a daily reset, do not sleep on it.
If you are calling the API with raw fetch or curl, copy the decision rule, not a sleep-and-loop. Check the status. Read Retry-After. Branch. Log the body so a human can tell a rate window from a credit pause without a packet capture.
Then act on the pause. A rate-limit 429 that keeps returning after you honor the header means the key is too hot for the work: serialize callers, spread them across time, or stop the fan-out. A credit-cap 429 means the meter is empty for that key or that day. Raise a spend cap on purpose, after you looked at usage, or wait for the daily reset at 00:00 UTC. None of those actions belong in an automatic retry loop.
The interactive playground is for you to see a 429 with your own eyes. Live `llms.txt` and `llms-full.txt` are for an agent that needs the same rule without scraping HTML. `/agent-setup/prompt.md` is an executable setup checklist a coding agent can run. ChatGPT connects to the same MCP server; see https://picxstudio.com/developers/mcp. Same key type. Same 429. Same decision.
A 429 you retry without reading is a guess. A 429 you classify is an API.
FAQ
Should every HTTP 429 be retried automatically?
No — retry a 429 only when it is a rate-limit response (it carries X-RateLimit-* headers and a short Retry-After), and only with a bounded attempt count. A credit-cap 429 should be surfaced, not retried.
How do I tell a rate-limit 429 from a credit-cap 429?
Read Retry-After first: a short wait is a request window, while a missing header or a wait that points at a daily reset is a spend or quota pause. Rate limits count calls; credit caps count credits per generation.
Do the PicX Python and JavaScript SDKs retry 429 responses?
Yes, with limits: the picx-ai SDKs retry 429, 5xx, and network errors, honor Retry-After, and do not retry other 4xx. After retries are exhausted they raise RateLimitError with retry_after (Python) or retryAfter (JavaScript).
What should a coding agent do when image generate returns 429?
If Retry-After is a short window, wait once and retry once; otherwise tell the user the key is paused and stop the tool loop. Do not re-POST a generate that already returned a hosted CDN URL.


