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

# Rate Limits

> The two-tier rate-limit model: per-wallet request-budget classes and the per-instrument order-rate limit.

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`](#runtime-introspection).

## 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**.

| Class          | Applies to                                               | Budget it decrements           |
| -------------- | -------------------------------------------------------- | ------------------------------ |
| `non_matching` | Read/query methods and non-order mutations (the default) | Per-wallet non-matching budget |
| `matching`     | Order-placing / matching methods                         | Per-wallet matching budget     |
| `endpoint`     | A method with its own dedicated per-method budget        | Per-`(wallet, method)` budget  |
| `custom`       | Methods that apply their own limiting logic              | Method-specific                |

Across the 100 JSON-RPC methods the classes are distributed:

<CardGroup cols={4}>
  <Card title="non_matching" icon="book-open">
    **92** methods
  </Card>

  <Card title="matching" icon="bolt">
    **5** methods
  </Card>

  <Card title="endpoint" icon="crosshairs">
    **1** method
  </Card>

  <Card title="custom" icon="wrench">
    **2** methods
  </Card>
</CardGroup>

<AccordionGroup>
  <Accordion title="matching (5)">
    `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.
  </Accordion>

  <Accordion title="endpoint (1)">
    `cancel_all` — decrements its own per-`(wallet, method)` budget so a bulk
    cancel doesn't consume the shared matching budget.
  </Accordion>

  <Accordion title="custom (2)">
    `cancel_by_label` and `public/get_all_live_instruments` opt out of the
    uniform dispatch and apply their own logic. `cancel_by_label` decrements the
    matching budget when scoped to a single instrument, otherwise a dedicated
    per-method budget; `public/get_all_live_instruments` is a public read with
    no per-request decrement, bounded only by the per-IP request and connection
    limits.
  </Accordion>
</AccordionGroup>

<Note>
  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.
</Note>

### Budget & window semantics

A request budget is a **fixed window**. The number of points available in each window is:

```text theme={null}
points_per_window = TPS × window_seconds
```

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.

<Warning>
  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`.
</Warning>

## Concrete limits

<Info>
  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`](#runtime-introspection) — the
  authoritative, live source for your budget.
</Info>

| Limiter                              | Scope                      | Mainnet reference             |
| ------------------------------------ | -------------------------- | ----------------------------- |
| Non-matching budget                  | Per wallet                 | 5 TPS → 25 points / 5s window |
| Matching budget                      | Per wallet                 | 1 TPS → 5 points / 5s window  |
| `cancel_all` budget                  | Per `(wallet, method)`     | 1 TPS → 5 points / 5s window  |
| Public request budget                | Per IP                     | 5 TPS → 25 points / 5s window |
| Concurrent WebSocket clients         | Per IP                     | 4                             |
| Per-instrument bucket — perps / spot | Per `(wallet, instrument)` | refill 1 token/s, capacity 5  |
| Per-instrument bucket — options      | Per `(wallet, instrument)` | refill 1 token/s, capacity 5  |

## 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.

<CodeGroup>
  ```typescript TypeScript (SDK) theme={null}
  import { DeriveClient } from '@derivexyz/derive-ts';

  // public/getRateLimits is a keyless public method — no wallet or login needed.
  const client = new DeriveClient({ network: 'mainnet' });

  // No SDK wrapper: use the typed send() escape hatch. Takes no params.
  const limits = await client.send('public/getRateLimits', null);
  console.log(limits.remaining_matching, limits.remaining_non_matching);

  ```

  ```python Python (SDK) theme={null}
  # Coming soon.
  ```

  ```rust Rust (SDK) theme={null}
  // Coming soon.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.derive.xyz/v3/public/getRateLimits \
    -H "Content-Type: application/json" \
    -d '{}'
  ```

  ```json Request theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "public/getRateLimits",
    "params": {}
  }
  ```

  ```json Response theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
      "remaining_matching": {
        "remainingPoints": 5,
        "msBeforeNext": 0,
        "consumedPoints": 0,
        "isFirstInDuration": true
      },
      "remaining_non_matching": {
        "remainingPoints": 24,
        "msBeforeNext": 3120,
        "consumedPoints": 1,
        "isFirstInDuration": false
      },
      "remaining_per_endpoint": {
        "cancel_all": {
          "remainingPoints": 5,
          "msBeforeNext": 0,
          "consumedPoints": 0,
          "isFirstInDuration": true
        }
      },
      "remaining_connections": {
        "remainingPoints": 3,
        "msBeforeNext": 0,
        "consumedPoints": 1,
        "isFirstInDuration": false
      }
    }
  }
  ```
</CodeGroup>

<ResponseField name="remaining_matching" type="object" required>
  Live state of the per-wallet matching budget.
</ResponseField>

<ResponseField name="remaining_non_matching" type="object" required>
  Live state of the per-wallet non-matching budget.
</ResponseField>

<ResponseField name="remaining_per_endpoint" type="map<string, object>" required>
  Per-method budgets, keyed by method name (e.g. `cancel_all`).
</ResponseField>

<ResponseField name="remaining_connections" type="object">
  Live state of the per-IP concurrent-connection limiter. Present on WebSocket
  only.
</ResponseField>

Each budget object carries:

<ResponseField name="remainingPoints" type="integer" required>
  Points (tokens) left in the current window.
</ResponseField>

<ResponseField name="msBeforeNext" type="integer" required>
  Milliseconds until the next request is allowed; `0` when a request may go
  through now.
</ResponseField>

<ResponseField name="consumedPoints" type="integer" required>
  Points consumed in the current window.
</ResponseField>

<ResponseField name="isFirstInDuration" type="boolean" required>
  Whether this is the first request in the current window.
</ResponseField>

<Note>
  `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.
</Note>

## 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](/error-codes) for the full catalogue.

| Code     | Message                                                 | Raised by                                                                                                                   |
| -------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `-32000` | `Rate limit exceeded`                                   | Per-wallet / per-IP request budget **or** the per-instrument bucket. The `data` field carries a `Retry after {ms} ms` hint. |
| `-32100` | `Number of concurrent websocket clients limit exceeded` | Per-IP connection-count limiter. The WebSocket receives a `connectionLimitExceeded:` text frame and is then closed.         |

```json Rejection theme={null}
{
  "jsonrpc": "2.0",
  "id": 42,
  "error": {
    "code": -32000,
    "message": "Rate limit exceeded",
    "data": "Retry after 3120 ms"
  }
}
```

<Tip>
  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.
</Tip>


## Related topics

- [Get remaining rate limits for the connection](/api-reference/system/get-remaining-rate-limits-for-the-connection.md)
- [Error Codes](/error-codes.md)
- [Connecting over WebSocket](/connecting.md)
