Rate limits
This page describes the request budgets of the gateway and the mempool bound of the node.
Preview. The gateway enforces the limits below. The bare node accepts traffic from authenticated mTLS peers on its own terms. That path is for trusted infrastructure only. In production, do not expose 8080 to the open internet.
Summary
- There are two budgets: a weight budget for each IP (every HTTP request) and a request-count budget for each account (signed
/exchangetraffic). POST /infocosts 1 weight, or 2 for the heavy types.POST /exchangecosts 5.POST /evmcosts 1 for each batch element.- The WebSocket costs no weight. The upgrade, a
subscribe, anunsubscribeand every pushed message are free. A cap of 64 subscriptions for each connection bounds the socket instead. - A burst spends a token bucket. The refill rate limits sustained traffic.
- A
429has no retry hint. Pace the client on the refill rate below.
Budgets
| Budget | Limit (default) | Refill | Burst | Exemption |
|---|---|---|---|---|
| Per-IP weight | 1200 weight / minute | 20 weight / second | 1200 (full bucket) | allowlisted IPs exempt |
Per-account /exchange requests | 1200 requests / minute | 20 requests / second | 1200 (full bucket) | metaliquidity-set signers exempt |
| WS subscriptions per connection | 64 | — | — | allowlisted connections exempt |
- The per-IP budget covers every HTTP request that reaches the gateway, signed or not. Allowlisted IPs bypass it. The operator designates these IPs for market makers and infrastructure.
- The per-account bucket applies to signed
/exchangewrites. It counts one token for each request, not the weight of the request. The same request also pays its weight of 5 against the per-IP bucket. The two budgets count different units for one call. - The per-account bucket is the lowest step of a ladder. The ladder scales with the committed 30-day trailing volume of the account. The fee tiers read the same volume figure. A high-volume account gets a larger bucket. An account with no volume gets the lowest step. An account whose volume read the gateway cannot complete also gets the lowest step. The ladder never lowers a budget.
- Accounts in the metaliquidity operator set are exempt from the per-account bucket. These accounts are the allowlisted vault-strategy signers. A signature proves the exemption. The gateway charges an unproven claim like any other caller.
- WS: a connection has at most 64 active subscriptions. The gateway rejects a 65th subscribe. Allowlisted connections are exempt from the cap.
The limits are operator configuration, not governance parameters. The volume ladder is the one input that comes from committed chain state.
user_rate_limit does not report the budget. The native
user_rate_limit read returns the action
counters of the account (last_nonce, pending_count, lifetime_count). It
does not return bucket state, and no read does. Track your own spend against
the table above.
Planned read. A GET /limits route that publishes the static per-IP and
per-account configuration is not implemented. The gateway serves no such path
today. These are the configured defaults:
{
"per_ip": {
"weight_per_minute": 1200,
"burst": 1200,
"refill_per_second": 20
},
"per_account_exchange": {
"requests_per_minute": 1200,
"burst": 1200,
"refill_per_second": 20
},
"ws_subs_per_conn": 64
}
Weight by endpoint
| Endpoint | Weight |
|---|---|
POST /info (most types) | 1 |
POST /info l2_book, markets | 2 |
POST /info user_fills | 2 |
POST /exchange | 5 |
POST /evm (single request) | 1 |
POST /evm (batch array) | 1 per element, minimum 1 |
POST /faucet (testnet) | 1 |
WS upgrade, subscribe, unsubscribe, pushed message | 0 |
WS post frame | the weight of the route it lowers onto |
A client that sends one order per second and polls account_state once per second spends 5 + 1 = 6 weight/s = 360 weight/min. That is well within the budget.
A request that the node refuses still costs weight. The gateway charges an
unknown /info type, a malformed body and a rejected action, because it must
read the request to find the problem. A malformed request is never cheaper. An
/evm array whose element count cannot be parsed pays the full batch cap,
100.
A subscribe is free today, and it does not bypass the budget. The WS upgrade
is mounted outside the per-IP middleware, and the subscribe path has no
limiter, so neither spends weight. The 64-subscription cap over a shared
upstream feed bounds the socket. One more subscriber adds no node work. The
post frame is the exception. It dispatches into the same routes as a REST
call, so it pays the same weight against the same per-IP bucket: /info by its
type, and /exchange = 5.
An EVM batch does not save weight. The gateway dispatches each element
independently, so a 40-element array costs 40. That is the same as 40 separate
calls. A batch saves round trips, not budget. The cap for a batch is 100
elements. The gateway rejects a larger batch whole with JSON-RPC -32600, and
it charges the refusal at the cap. The gateway never trims the array, so a
caller never has to find out which prefix ran. See
the EVM JSON-RPC page.
/exchange order batching is different: it does save weight. One request with
10 legs costs 5, not 50. The rules differ because the node admits an
/exchange batch once, as one action. An EVM batch is N independent calls in
one envelope.
Per-account requests
When a request is signed, the gateway reads the acting account from the body. It counts one token against the budget of that account, in addition to the per-IP weight.
| Sender state | Counted against |
|---|---|
Anonymous (no signature, e.g. POST /info) | per-IP weight only |
| Signed by master | per-IP weight + 1 per-account token |
| Signed by agent | per-IP weight + 1 token on the MASTER's bucket |
All agents of one master share the budget of the master. The bucket keys on the account that the action names, not on the key that signed it. A client that sends a high volume from one IP for one account hits the tighter of the two budgets.
Mempool bound
The mempool bound is separate from the rate limits. The node enforces it, not the gateway. The node keeps pending actions in a bounded queue of 8192. The bound is global, not per account. When the queue is full, the node drops the oldest pending action.
An accepted /exchange response is an acceptance, not a commit. The node
already acknowledged an action that this eviction drops, so the caller does not
see the drop. Before you treat an order as resting, confirm it from the
committed side: the order_updates feed, or a poll.
This applies only to a sustained flood. At a healthy block time of about
100 ms, the queue drains much faster than a client within the rate limits fills it.
Burst behaviour
The buckets fill to burst and refill at refill per second. A burst of N ≤ burst requests passes at once. The gateway then throttles later requests to the refill rate.
A 429 tells you that the bucket is empty, and nothing more. The body is the
standard failure envelope with error.code
RATE_LIMITED. It names no wait: there is no Retry-After header and no
retry_after_ms field. Calculate the wait yourself. At 20 weight/second, a
weight-1 request is affordable again 50 ms later. A weight-5 /exchange is
affordable again 250 ms later. For batch jobs, pace on the client side. For
interactive workloads, use exponential backoff.
Strategies
Order-flow bot
- Rate-limit on the client before the gateway does. The per-IP weight budget binds first.
/exchangecosts 5, so 20 weight/second gives 4 orders per second from one IP. - Use
Orderbatching. One request with 10 orders costs 5 weight, the same as one order. The per-account budget counts requests, not legs. - Use
BatchModifyinstead of N separateModifyOrders. - Get market data from the WS feed. Do not poll
/infofor it.
Market-data consumer
- Subscribe to WS channels (
l2_book,trades,fills). Do not poll. - A subscribe and an in-stream message both cost 0, so the feed spends no budget.
- On reconnect, subscribe again from a new snapshot. There are no resume tokens. Stay within the cap of 64 subscriptions for each connection.
High-frequency liquidator
- Run your own self-hosted node (mTLS-authenticated,
localhost:8080). It bypasses the limits of the public gateway. - This requires infrastructure that peers with a validator.
- Public gateway access is sufficient for workloads of tens of orders per second. It is not sufficient for HFT.
Throttle and recovery sequence
Override channels
| Channel | Notes |
|---|---|
| mTLS peer of a validator | Bypasses the gateway rate limits. This is the trusted path |
| Allowlisted IP (operator-side) | Exempt from the per-IP weight bucket |
| metaliquidity operator set | Exempt from the per-account /exchange bucket |
| Trailing-30d volume | Raises the per-account bucket automatically. No application is necessary |
The public defaults assume that none of these applies.
See also
- Errors
- WS subscriptions
- Idempotency: how to retry within the rate-limit budget
FAQ
Show FAQ
Q: Are limits per key pair or per address? A: Per address. All agents of the same master share the budget of the master, because the bucket keys on the account that the action names.
Q: Can I batch one order across 10 markets to save weight?
A: Yes. Order { orders: [<10 legs>] } costs 5 weight, not 50.
Q: Do /info polls and WS subscribes share a budget?
A: No. A subscribe costs nothing, so it cannot empty the bucket that an /info poll spends. The one WS frame that shares the per-IP bucket is post. It lowers onto /info or /exchange and pays the weight of that route.
Q: How do I read retry_after_ms from a 429?
A: You cannot. The gateway does not send one. Calculate the wait from the refill rate: 20 weight per second.
Q: Does testnet use the same limits? A: Testnet runs the same defaults unless the operator raises them. Do not tune your client against testnet. Use the table above for the network that you deploy to.