Skip to main content

The envelope

Every failure shares one shape:
Branch on 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.
Read the live limits from GET /status, which publishes them as a machine-readable table, rather than hardcoding them. The published values are provisional ahead of general availability and will change.

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: true rather 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.
Retry on 429 and 5xx. Do not retry a 400 or a 401 without changing something: a rejected signature will be rejected identically every time.