api_error. Something failed on our side before a run could
start. This is our fault, not your request’s.
The money contract still holds: if a reservation was written before the failure, it
is settled or released from the durable record — the reaper reconciles any orphaned
reservation, so a crashed request can never pin your balance indefinitely. If a run
did start and then died, that reaches you as status error on the run (a $0-beyond-
receipts outcome, stated), not as this HTTP error — see
“Failures that are NOT errors”.
internal is also the conservative default of the
stream-terminal codes: a run that died in a way we
could not classify ends its stream with
{ "phase": "error", "code": "internal" } — a stream code on a started run, not
this HTTP error. Handle unknown stream codes the same way.
The shape
How to fix
- Retry with the same
Idempotency-Key. That’s the designed recovery: if the original actually went through, the retry replays it (fn-idempotent-replay: true) instead of double-billing; if it didn’t, the retry runs it. Use backoff; give up after a few attempts. - If it persists, save
request_idand contact support — the id joins directly to our gateway logs and, where a run started, to the session record. - Check whether a run was created despite the error:
GET /v1/sessions?limit=5.
Reproduce it
You can’t provoke this one on purpose, and there is deliberately no test sentinel for it — a 500 is our bug, not an API behavior to rehearse. Rehearse the run-level failure ending instead, which is the one your code should handle deliberately:FN_TEST_ERROR streams a run that ends error with the
reservation released.