Skip to main content
On the REST API, errors arrive in one of two places, and the difference matters when you’re writing the handling code.

Before the stream

Validation, auth, and capacity are checked up front. These are ordinary JSON responses with a 4xx status. No credit is charged.

On the stream

Anything that goes wrong during generation arrives as a terminal error event, after HTTP 200 has already been sent. Whether a credit was charged depends on the code.

Shape

Rejections before the stream:
Failures during generation, as the final event:
Match on code. The message is for humans and may be reworded.

Before the stream

rate_limited has two causes with the same code: the per-key call budget and the per-workspace concurrency limit. Either way it’s safe to retry after a short wait, and nothing has been charged. See Credits and limits.

On the stream

Keep the request_id from the accepted event and log it next to anything you report. It’s what identifies your generation in our logs.

Handling them

The main thing to get right is handling the two places separately:
A reasonable default: retry rate_limited automatically with backoff, retry generation_failed once, look for the email before retrying timed_out, and never retry the 400s or 401s, because they’ll fail the same way until you change something.

Over MCP

MCP tools don’t return codes. A failed tool call comes back as an error result whose text is meant for the assistant to read and act on, and most assistants relay it to you. The common ones: An expired OAuth sign-in isn’t a tool error. Your client receives HTTP 401 and refreshes on its own, or asks you to sign in again.