Skip to main content
Rate limiting is two-tier. Every request first passes a per-wallet (or per-IP) request budget, and order-flow requests additionally pass a per-instrument order-rate limit. A request that exceeds either tier is rejected with a JSON-RPC error — nothing is queued. Read your live budgets at any time with public/getRateLimits.

Tier 1 — Request budget (per wallet)

Every method carries a rate-limit class (shown on each method’s page in the API Reference tab). The class selects which per-wallet budget the request decrements. Authenticated traffic is keyed by wallet; unauthenticated/public traffic is keyed by IP. Across the 128 JSON-RPC methods the classes are distributed:

non_matching

95 methods

matching

5 methods

proven

20 methods

endpoint

7 methods

custom

1 method
order, replace, cancel, cancel_by_instrument, cancel_by_nonce. All order-flow writes share one per-wallet matching budget, regardless of how many connections or session keys the wallet has open.
private/transfer_spot, private/transfer_spot_external, private/transfer_positions, private/withdraw, private/liquidate, private/set_session_key, private/edit_session_key, private/delete_subaccount, private/update_whitelisted_recipients, private/change_subaccount_label, private/set_mmp_config, private/reset_mmp, private/create_vault, private/update_vault_info, private/mint_vault_shares, private/burn_vault_shares, private/request_vault_deposit, private/request_vault_withdraw, private/cancel_all_vault_requests, private/force_burn. Each call changes account state, so all of them share one small per-wallet budget (proven in remaining_per_endpoint), independent of the matching and non-matching budgets.
cancel_all, private/send_quote, private/replace_quote, private/cancel_quote, public/register_deposit_address, private/register_bridge_deposit, private/register_withdraw_address. Each decrements its own per-(wallet, method) budget instead of a shared one, so a bulk cancel or a market maker’s quoting doesn’t consume the matching or non-matching budget.
cancel_by_label decrements the matching budget when instrument_name is set, and its own per-method budget otherwise.
Budgets are wallet-scoped, not per-subaccount or per-session-key. All concurrent connections and session keys belonging to one wallet share a single budget. Public, pre-authentication traffic is limited per source IP, along with a cap on simultaneous WebSocket connections per IP.

Budget & window semantics

A request budget is a fixed window. The number of points available in each window is:
Every request costs one point; there is no per-method weight table. When the window elapses the budget resets. Because it is a fixed window (not a sliding one), a client can briefly emit up to two windows’ worth of requests across a window boundary.

Tier 2 — Per-instrument order-rate limit

Order-flow requests (order, replace, cancel, …) pass a second limiter: a per-instrument token bucket, keyed by (wallet, instrument).
  • The refill rate is a per-second TPS chosen by asset type — one value for perps and spot, another for options.
  • The bucket’s capacity (its burst allowance) is refill_rate × burst_multiplier.
  • Unlike the request budget’s fixed window, the bucket refills continuously, so it does not permit a window-boundary double-burst.
An order-placement request is limited twice: once by the matching request budget and once by the per-instrument bucket. A rejection can come from either tier, and both surface the same error code (see below), so a rejection alone does not tell you which tier tripped. The per-instrument limits are not reported by public/getRateLimits.

Concrete limits

All limits are deployment-specific and read from configuration at startup — code defaults, testnet, and mainnet differ. Treat the values below as a mainnet reference snapshot, not a contract, and always read your live budget with public/getRateLimits — the authoritative, live source for your budget.

Runtime introspection

public/getRateLimits returns your live request budgets. It is itself a non_matching method, so calling it costs one non-matching point. It takes no parameters.
object
required
Live state of the per-wallet matching budget.
object
required
Live state of the per-wallet non-matching budget.
map<string, object>
required
Per-method budgets, keyed by method name (e.g. cancel_all), plus the shared proven budget. A budget appears once the wallet has used it.
object
Live state of the per-IP concurrent-connection limiter. Present on WebSocket only.
Each budget object carries:
integer
required
Points (tokens) left in the current window.
integer
required
Milliseconds until the next request is allowed; 0 when a request may go through now.
integer
required
Points consumed in the current window.
boolean
required
Whether this is the first request in the current window.
getRateLimits exposes only the request-budget tier. The per-instrument token buckets are not reported, so perp/option instrument limits cannot be discovered at runtime.

Rejection error codes

When a request exceeds a limit it is rejected — never queued — with a JSON-RPC error. Codes -32000 through -32099 are reserved for rate-limit errors. See Error codes for the full catalogue.
Rejection
Back off using msBeforeNext from getRateLimits or the Retry after {ms}{" "} ms hint on a -32000 rejection. Because order flow is limited by both the matching request budget and the per-instrument bucket, spread order flow across instruments and keep a margin below your reported budget.