Get started
Errors
Every error response has the same shape. The code is stable and safe to switch on; the message is human-readable and may change.
Error bodyjson
{
"error": {
"code": "invalid_state_transition",
"message": "Invalid B2B transition: released → cancelled",
"current_state": "released",
"allowed_next_states": []
}
}HTTP status policy
| Status | Meaning |
|---|---|
| 400 | Request shape invalid (validation, unknown cursor, etc.) |
| 401 | Authentication missing or invalid (raw key, session JWT, access token) |
| 403 | Authenticated but lacks scope / permission / account suspended |
| 404 | Resource not found OR not owned by the caller (cross-tenant denial) |
| 409 | State-machine conflict — includes current_state + allowed_next_states |
| 429 | Rate limit exceeded — Retry-After header supplied |
| 500 | Unexpected error; correlate via server logs |
| 502 | Downstream (Stripe) error — safe to retry idempotent calls |
Tenancy semantics
Cross-tenant access returns 404, not 403. The API never discloses the existence of resources the caller does not own. Only the owning account sees a 404 for genuinely deleted resources; everyone else sees the same 404 they'd get for a resource that never existed.
Retry guidance
Safe to retry: any 502 (downstream Stripe), any 5XX, any 429 after the Retry-After header. Mutating endpoints (create, cancel, release) are idempotent either by external_reference (create) or by terminal-state absorption (cancel/release), so retries don't duplicate work.
Do not retry: 400, 401, 403, 404, 409. The request will fail the same way every time until you fix the call site or the underlying resource state.