Skip to main content
Every error is a JSON envelope with a stable code, a message for a person, and sometimes more detail. Branch on the code, never on the message text.

Codes

Codes are an open set: a new one may appear within a version. Treat a code you do not know by its HTTP status.

Refusals that carry a request id

A submit that Mage records before refusing it (402, 403, 410, 429, 451, and a 400 or 500 raised by the run itself) carries request_id in the envelope. Its status read reports how the request ended, and its Gems ledger is visible in the app, even if you sent no idempotency key. The key you sent with it is spent: a retry with the same Idempotency-Key replays this failed request instead of submitting again. After fixing the cause, submit a new attempt with a new key.

Failures on a status read

A request that was accepted and then failed reports status: failed with error.code set to one of these, and billing shows both the charge and the refund. The text of a model’s own error is never forwarded.

Retry policy

  • Retry status reads on network errors and 5xx responses, with exponential backoff and jitter.
  • Retry a submit that got no response (a timeout or a network error) with the same Idempotency-Key, so it cannot charge twice.
  • An error envelope that carries request_id has consumed its key: the refusal is recorded as that request, and the same key replays it. Fix the cause, then submit again with a new key.
  • Do not retry 401, 403, 404, 409, or 451, and do not retry a 400 without changing the request.
  • Wait on 429 and on 402 until requests finish or Gems are added, then submit again, with a new key when the envelope carried request_id.
  • Give every retry loop a cap on attempts and a deadline.
If you contact mage@mage.space about a request, include its request_id and roughly when it happened.
Last modified on September 17, 2026