The envelope
Every failure shares one shape:error.code. Treat error.message as display text, because it is not stable enough to parse.
Codes
ORDER_REJECTED reasons
These are rejections at submission. An order the engine accepts can still be rejected later,
which surfaces as
rejectCode on the order itself rather than as an API error. See
Accepted is not filled.Rate limits
Budgets are per key for submits, cancels, mass-cancels, and reads, with additional IP tiers on the public routes. Moving traffic to the WebSocket does not raise them. Responses from rate-limited routes carry the current state:
Not every response carries them.
GET /health and requests that fail authentication are answered before the limiter runs, so all three headers are absent; treat them as present-when-metered rather than guaranteed.
A 429 additionally carries retryAfterSeconds, limit, remaining, and resetAt in the error body. Respect retryAfterSeconds rather than retrying on a fixed interval.
Mass-cancel fan-out never consumes the per-order cancel budget, so withdrawing all resting orders during a halt is not rate-limited.
Retrying safely
Most write routes are safe to retry, by design:- Order submission is idempotent by order hash. Resubmitting the identical signed order returns the original with
idempotent: truerather than doubling the position. - Registration is idempotent.
- Credential issuance is derive-or-create at a given nonce, so a retry returns the same credentials with
existing: true.
429 and 5xx. Do not retry a 400 or a 401 without changing something: a rejected signature will be rejected identically every time.