> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arcus.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> How requests are weighted and throttled across REST, WebSocket, and the trading path

Arcus throttles traffic in two independent layers. Both can reject the same request, and a request must pass both to succeed:

* **Per-IP weight limits** — every REST request (and WebSocket message) costs a *weight*. Each source IP gets a token budget that refills over time; expensive endpoints drain it faster.
* **Per-subaccount trading limits** — order placement and cancellation draw from two pools keyed to your subaccount, independent of IP. These scale with your realized trading volume.

<Note>
  Limits are enforced globally across all gateway nodes (state lives in a shared store), so spreading requests across connections or hitting different nodes does not raise your effective budget.
</Note>

## Per-IP weight limits

Every IP gets a token bucket holding **1,500 weight**, refilling continuously at **1,500 weight per minute** (25 weight/second). Each request deducts its weight before the handler runs. When the bucket can't cover a request's weight, the request is rejected.

### Rejection contract

A throttled request returns:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: application/json

{"error":"rate limited"}
```

`Retry-After` is the whole number of seconds to wait before the bucket holds enough tokens to retry (minimum `1`). Arcus does **not** return `X-RateLimit-*` headers — the budget and per-endpoint weights are published here instead, so you can account for cost client-side rather than discovering it per response.

### Weight tiers

Endpoints fall into a handful of tiers by how much work they cost the backend. With a 1,500/min budget that's roughly **750 cheap reads**, **75 standard list calls**, or **12 `cancelAllOrders`** per minute from a single IP.

| Weight  | Endpoints                                                                                                                                                                                                                                                                          |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **0**   | `health` — never touches the limiter (use it for liveness probes). **Single order writes** — `placeOrder`, `modifyOrder`, `cancelOrder` — are also **free on the IP layer**; they're governed entirely by the per-subaccount trading pools below.                                  |
| **1**   | `/` (root)                                                                                                                                                                                                                                                                         |
| **2**   | Cheap single-object reads — `bbo`, `mids`, `account`, `positions`, `order`, `feeTiers`, `leverages`, `accountStats`, `rateLimit`                                                                                                                                                   |
| **20**  | Standard data, lists, and management — `prices`, `markets`, `trade`, `trades`, `candles`, `portfolio`, `openOrders`, `orders`, `fills`, `funding`, `fundingRates`, `accountTransferUpdates`, `apiKeys`, `createApiKey`, `revokeApiKey`, `userPreferences`, all affiliate endpoints |
| **125** | Heavyweight writes — `cancelAllOrders` (fans out across your whole book), `setLeverage`, `withdraw`                                                                                                                                                                                |

**Batch order writes** (`batchPlaceOrders`, `batchModifyOrders`, `batchCancelOrders`) are also free on the IP base; they incur only a post-flight per-item charge of `floor(N/40)` weight for a batch of `N` — so a batch of up to 39 orders costs **0** IP weight. As with single writes, the real limit is the per-subaccount trading pool (each element charges the pool 1).

**List endpoints add a per-item charge** on top of their base weight, computed from the rows actually returned: `total = base + floor(items / N)`, where `N` is `20` for most lists (`60` for `candles`). Page sizes are capped (2,000 rows for most lists, 1,000 for `accountTransferUpdates`), so the worst-case add-on is bounded — e.g. a full 2,000-row page of `fills` costs `20 + 100 = 120`. `l2OrderBook` works the same way on order-book depth: `2 + floor(nLevels/20)`, maxing at weight 7 for a 100-level book. Because the row count isn't known until the query runs, this charge is applied after the response is sent, so a single oversized page can briefly drive your bucket negative; your next request then waits for it to recover.

## Per-subaccount trading limits

Order placement and cancellation draw from two pools scoped to your **subaccount** (address + account index), enforced on both the REST and WebSocket order paths. This is the address-based analogue to the per-IP layer: it bounds how fast a single account can act, regardless of how many IPs or connections it spreads across.

| Pool       | Starting cap | Charged by                                                                            |
| ---------- | ------------ | ------------------------------------------------------------------------------------- |
| **Order**  | 20,000       | `placeOrder` / `modifyOrder` — 1 per order (N for a batch of N)                       |
| **Cancel** | 40,000       | `cancelOrder` — 1 per cancel (N for a batch of N); `cancelAllOrders` — flat **1,000** |

The pools are independent: a cancel-heavy strategy can drain the cancel pool while the order pool sits nearly full, or vice versa.

### Volume-based replenishment

Each pool's effective cap grows with your **lifetime realized notional** — the more you trade, the more headroom you get:

```
effective cap = starting cap + lifetime_notional_usd / 0.10
```

Every **\$0.10** of realized fill notional adds **1** unit of pool headroom (so \$1 of volume buys **10**). Roughly **\$100K** of cumulative volume buys **+1,000,000** headroom on each pool, and high-volume market makers (≥ \$1M lifetime) are effectively unconstrained. The counter is cumulative and never decays, so caps only ever rise.

### When a pool is empty

Past the effective cap, a slow **drip** takes over: **1 action per 10 seconds** per pool. This keeps a depleted account alive at a trickle rather than hard-failing, while still throttling abuse. Charges that exceed the remaining pool are rejected the same way as the IP layer (with a retry hint).

<Note>
  Charges are refunded automatically if an accepted order fails to publish downstream (a transient internal error). Engine rejections do **not** refund — the matching-engine work was already consumed.
</Note>

### Checking your remaining budget

`GET /v1/rateLimit?address=<0x...>&account_index=<n>` returns a live snapshot of both pools:

```json theme={null}
{
  "address": "0x...",
  "accountIndex": 0,
  "order":  { "used": 1200, "cap": 12000, "nextAvailableMs": 0 },
  "cancel": { "used": 0,    "cap": 22000, "nextAvailableMs": 0 }
}
```

* `used` — units consumed since your last reseed.
* `cap` — current effective cap (starting cap plus volume replenishment).
* `nextAvailableMs` — `0` when you have headroom; otherwise the milliseconds until the next drip token frees up.

## WebSocket limits

WebSocket connections are bounded per IP, in addition to the per-message weight charged against your IP bucket:

| Limit                        | Value       | Scope                              |
| ---------------------------- | ----------- | ---------------------------------- |
| Simultaneous connections     | 50          | per IP                             |
| New connections              | 50 / min    | per IP                             |
| Subscriptions per connection | 100         | per socket                         |
| Subscriptions                | 1,000       | per IP (across all connections)    |
| Outbound messages            | 1,000 / min | per IP                             |
| In-flight `post` messages    | 50          | per connection                     |
| Connection lifetime          | 24 hours    | per connection (auto-closed after) |

Only **client→server** messages count against the outbound-message rate — `subscribe`, `unsubscribe`, and `post` / `get` RPCs. Server-pushed `channel_data` updates are free. A rejected new connection still consumes a new-connection token, so a client hammering the connection cap burns its own budget faster.

## Handling rate limits

* **Read `Retry-After`** on a `429` and back off for at least that long. A blind retry loop will keep losing.
* **Prefer WebSocket subscriptions over REST polling** for anything that changes frequently (order book, fills, account state). One subscription replaces a stream of weighted REST calls.
* **Batch order operations.** A batch of `N` costs only `floor(N/40)` IP weight (free on the IP base, like single writes) — far cheaper than `N` separate calls — though it still charges per item against the trading pool. You also save the per-request network round trips.
* **Poll `GET /v1/rateLimit`** if you run an aggressive order/cancel loop, and slow down as `used` approaches `cap`.
* **Request smaller pages** on list endpoints — a large page adds per-item weight after the fact.

If your use case legitimately needs higher limits (e.g. a high-volume market maker), reach out about IP allowlisting for your egress addresses.
