> For the complete documentation index, see [llms.txt](https://docs.nexus.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nexus.xyz/api-reference/guides/rate-limits.md).

# Rate Limits

The Exchange does not budget your traffic in requests per second. It budgets **weight per second**: every request is charged a cost, most requests cost one unit, and a few cost considerably more. A client that paces itself by counting requests will be refused while its own counter still looks healthy, which is the single most common integration surprise on this surface.

This page explains the model — what things cost, which budgets are separate, and how to read the headers. The **normative** definition lives in the OpenAPI contract, published at [`nexus-xyz/nexus-exchange-api`](https://github.com/nexus-xyz/nexus-exchange-api) and served at `/openapi.json`: the "Rate limits" section of the API description owns header semantics, and each operation carries its own machine-readable cost. Where this page and the contract disagree, the contract is right.

### What a request costs

Your budget is a **token bucket that refills continuously at your tier's per-second rate**, with a capacity of exactly one second of tokens. Two consequences follow from that capacity: the sustained rate and the burst allowance are the same number — there is no multi-second reserve to bank by idling — and `remaining` can never exceed `limit`.

Most requests cost **1**. The exceptions:

| Cost                              | Applies to                                                                                    | Why                                                                                                 |
| --------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **5**                             | `GET /account/summary`, `GET /fills`, `GET /orders/history`, `GET /account/portfolio-history` | Each folds or scans a large per-account buffer — fills, order history, or the portfolio time-series |
| **`1 + floor(order_count / 40)`** | `POST /orders/batch`                                                                          | Up to 40 orders cost the same as one; every further 40 adds a unit                                  |
| **1**                             | every other operation the contract documents                                                  | The default                                                                                         |

So a Pro caller at `limit: 20` gets 20 ticker reads per second — **or 4 `/fills` reads**. Same budget, same tier, different arithmetic. Pace on the weight, not on the request count.

Two safety properties are worth knowing, because they bound the worst case. A single request's charge is capped at one second of tokens, so an oversized batch can never be permanently unsatisfiable — it drains the whole second once the bucket has refilled and goes through, rather than looping forever on a retry it could never afford. And a batch body the server cannot parse is charged the base unit rather than refused over weighting.

Rather than hardcoding the table above, read the cost from the contract: operations costing more than one unit carry **`x-nexus-rate-limit-weight`**, and those whose cost depends on the body also carry **`x-nexus-rate-limit-weight-formula`**. **For an operation the contract documents, absence of the marker means weight 1.** That is the form to build a client-side limiter against.

The qualifier is deliberate. The contract enumerates the supported surface, and that rule holds across it — but it is not a statement about any path that happens to answer. A route the contract does not list carries no marker, is not covered by the rule, and may be charged differently from what its absence suggests. Build against the operations the contract documents rather than against paths found by probing; that is the surface the weights, and this page, describe.

### Four budgets, not one

There are four independent resource classes. Spending one does not spend the others, and each refuses in its own way:

| Class                       | Covers                                                                          | Charged against                                                          |
| --------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **Requests**                | Every REST operation that is not an order write                                 | Your per-key and per-owner request buckets                               |
| **Trading actions**         | `POST` and `PATCH` under `/orders` — marked `x-nexus-rate-limit-class: trading` | A dedicated order bucket, the same per-second size as the request bucket |
| **Cancellations**           | `DELETE` under `/orders` — also marked `x-nexus-rate-limit-class: trading`      | A **separate** cancel bucket, again the same per-second size             |
| **WebSocket control plane** | Connections, subscriptions, and inbound client frames                           | Per-tier ceilings — see [WebSocket ceilings](#websocket-ceilings)        |

An order write is charged to the trading bucket **instead of** the request bucket, not in addition to it. That is the point of the split: a burst of polling cannot starve your order placement, and order flow cannot starve your reads. The corollary is the part to internalize — **a healthy `x-ratelimit-remaining` on your last read tells you nothing about your order-placement headroom.** They are different pools, and `GET /account/rate-limit` reports the request class only.

#### Cancellations never share a token with submission

Cancelling is the one action split out of trading into a budget of its own. A `DELETE` on the order surface — cancel-one, cancel-all, or cancel-by-market — is charged to the cancel bucket instead of the order bucket, so **a key that has spent its entire submission allowance placing orders still has a full, untouched allowance for pulling them back.**

This is the only deliberate asymmetry in the model, and the reason for it is worth stating plainly: submission can wait, risk reduction cannot. A limiter that refuses your cancel while a position runs against you has stopped being a fairness control and become a loss. So the rule is unconditional — exhausting submission can never refuse a cancel, because the two never draw on the same token.

Two things follow for a client.

* **Never infer your cancel headroom from a submission `429`.** The `order` bucket being empty says nothing at all about the `cancel` one. A client that backs its whole order surface off after one placement refusal has throttled the one call it should still be making.
* **A separate bucket is not a bypass.** Cancels are metered, at the same per-second tier rate as everything else, so a cancel loop can still `429` — carrying `bucket: cancel`, which is the only refusal that actually means your cancel channel is saturated. Honour `retry-after` on that one; it is real.

Amends are charged as submission, not cancellation. `PATCH` does not say whether an amend reduces or increases exposure, and a size-increasing amend is a submission by any reading — so the guarantee above is attached to the method that unambiguously reduces risk. If you need it, cancel.

`POST /orders/preview` is a trading action too, which catches people out: it is a write on the `/orders` surface and costs a trading-class unit exactly as placing an order does. Previewing before every order therefore halves your effective placement rate. Budget two trading-class charges per order placed that way, or skip the preview once you already know the sizing.

A caller presenting an HMAC key passes a **per-key** bucket and then the **per-owner** bucket for its tier; the effective ceiling is whichever binds first. `GET /account/rate-limit` reports that minimum, and polling it is free — it is the one operation that consumes no tokens, precisely so that pacing yourself cannot throttle you.

One wrinkle for anyone who has just been promoted: a key's own ceiling is recorded when the key is created (20/s by default) and a tier change does not rewrite it. A Market Maker account still using a key minted at the default can therefore be held at the key's number rather than the tier's. Read `/account/rate-limit` after a promotion instead of assuming the tier figure; mint a fresh key if the minimum reported is not the one you expect.

### Reading the headers

* On **every** authenticated response: `x-ratelimit-limit` and `x-ratelimit-remaining`.
* On a **`429` only**, additionally: `x-ratelimit-reset` (unix seconds) and `retry-after` (seconds, never below 1).

Do not expect the latter two on a success — a client that reads `x-ratelimit-reset` off a 2xx reads nothing.

**`remaining` and `retry-after` are deliberately in different units.** `remaining` is expressed in unit-cost requests: `x-ratelimit-remaining: 10` means ten weight-1 requests *or* two heavy ones. `retry-after`, by contrast, is derived from the weighted cost of the request that was actually refused. A limiter that reads `remaining` as "requests of the kind I am about to send" will over-send on heavy endpoints and 429 itself.

A refusal is `HTTP 429` with this body:

```json
{
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Order placement rate limit exceeded",
  "tier": "Pro"
}
```

Branch on `code`. The `message` names which pool bottlenecked — `Rate limit exceeded`, `API key rate limit exceeded`, `Order placement rate limit exceeded`, or `IP rate limit exceeded` — and is a **diagnostic**: its wording is not stable and must not be matched programmatically.

Unlike the `403` jurisdiction refusals, a `429` **is** retryable: honour `retry-after` and back off. Pacing off `x-ratelimit-remaining` beats discovering the ceiling by hitting it.

The free `GET /account/rate-limit` reports the same state without spending a token, for the request class:

```json
{
  "tier": "pro",
  "limit": 20,
  "remaining": 17,
  "reset_at_ms": 1750000000123
}
```

Two details to handle when you use both this endpoint and the headers. `reset_at_ms` is **milliseconds**, while the `x-ratelimit-reset` header is unix **seconds**. And the tier name is lower-cased here (`pro`) but not in the `429` body (`Pro`), so compare it case-insensitively rather than against a literal.

All three numeric fields are `null` for an `Unlimited` caller, which is bucketed per IP rather than per account.

### Tiers and current ceilings

Tiers are multipliers on one model, not different models. **Pro** is the default for every account. **MarketMaker** is admin-assigned — request it through your Nexus contact, see the [Market Maker Guide](https://docs.nexus.xyz/exchange/apis-and-rates/market-maker-guide). **Unlimited** exists for gateway keys that multiplex many users and is never assigned to a trading account.

| Tier          | Requests     | Trading actions                         | WS connections | WS subscriptions | WS inbound frames |
| ------------- | ------------ | --------------------------------------- | -------------- | ---------------- | ----------------- |
| `Pro`         | 20/s         | 20/s                                    | 5              | 50               | 10/s              |
| `MarketMaker` | 2,000/s      | 2,000/s                                 | 100            | 1,000            | 50/s              |
| `Unlimited`   | per-IP, 50/s | per-IP, 50/s — the same bucket as reads | exempt         | exempt           | exempt            |

Two limits apply regardless of tier. Unauthenticated market-data reads are bucketed **per client IP at 50/s**. And traffic whose client IP cannot be resolved at all is not admitted unthrottled — it shares one strict bucket of 5/s, so an unresolvable origin degrades to a low ceiling rather than to no ceiling.

Cancellations get their own budget at the Trading-actions rate — a further 20/s for `Pro`, 2,000/s for `MarketMaker` — rather than a column of their own above, because the two numbers are equal by construction.

`Unlimited` deserves one caveat, because its exemptions are narrower than the name suggests. Its order writes **and its cancels** are **not** exempt from rate limiting: they skip both the dedicated trading bucket and the cancel bucket and are charged to the same per-IP bucket as its reads, so neither the class independence above nor the cancel guarantee holds there — a gateway's order flow can be crowded out by its own polling, on the tier whose traffic mix is least predictable. And the WS ceilings it is exempt from are the *per-account* ones; the per-IP connection cap below still binds.

**These numbers are current defaults, not a contract.** They are deployment configuration — some of them still code constants — and they will change as the tier system is finalized. Read `/account/rate-limit` rather than hardcoding them.

### WebSocket ceilings

The WebSocket ceilings are a resource class of their own: independent of the REST request budget and of the trading-action budget, and exhausting one does not affect the others.

**Connections** are capped per account by tier (Pro 5, MarketMaker 100). A separate **per-IP cap — 5 by default** — is applied first, at upgrade time, and binds on every tier including `Unlimited` — so one origin address cannot reach the per-account figure on its own. A connection refused there gets an `HTTP 429` (`ws_conn_limit_exceeded`) on the upgrade request rather than a close frame, and the single-use stream token is spent either way: mint a fresh one before reconnecting.

**Subscriptions** are capped at the same number twice — per connection *and* across all of an account's connections. Opening more sockets therefore does not buy more subscriptions. Re-subscribing a `(channel, market)` key you already hold replaces it in place and is free. Exceeding the ceiling returns an `error` frame (`subscription_limit_exceeded`) rather than a status code; there are no HTTP responses once the socket is open.

**Inbound frames** — your subscribes, unsubscribes and pings — are limited to the sustained per-tier rate with a **2× burst** tolerated above it, so a reconnect-and-resubscribe storm is not penalized. Beyond that, over-limit frames are **dropped**, and you get one `error` notice per accounting window rather than one per frame (an inbound flood is not amplified into an outbound one). Sustained flooding — enough dropped frames inside one window — closes the connection with code **`1008`** (policy violation). A dropped frame is silently not applied: if you do not see a `subscribed` ack, re-send the subscribe after backing off rather than assuming it took effect.

### Budgets are per network

Each network is its own deployment, so **each network has its own buckets**. Spending on testnet does not reduce mainnet headroom when mainnet launches, and neither does the reverse. Credentials do not cross networks either — a key is bound to the network that minted it. See [Networks](/api-reference/guides/networks.md).

### Where enforcement lives today

One honest limitation, because it is visible from outside and it moves in your favour rather than against you.

Limiter state is held **in memory at the gateway process**, not in a shared store. Two things follow. First, it is not durable: a redeploy resets your buckets, and a tier promotion may briefly fall back to the base tier until it is re-applied. Second, when a network's gateway runs more than one replica, each holds its own buckets, so the *aggregate* ceiling a client observes can be higher than the published per-second figure, depending on how its connections land.

Do not design against that headroom. Treat the published number as the ceiling you are entitled to and pace to it: the extra is an artifact of where enforcement currently lives, it is not distributed evenly, and it goes away when counters move to a shared store. A client built to the published figure keeps working when that lands; one tuned to the observed aggregate will start seeing 429s.

### Designing for the limits

* **Batch instead of looping.** `POST /orders/batch` charges `1 + floor(n / 40)`, so 40 orders in one request cost a fortieth of 40 single submits.
* **Stream instead of polling.** Order book and trade data over WebSocket costs nothing against your request budget, and arrives sooner: the subscribe frame is one inbound frame, and the stream that follows is free.
* **Budget heavy reads at their real cost.** `/fills`, `/orders/history`, `/account/summary` and `/account/portfolio-history` cost 5 each. Polling all four every second costs 20/s — a Pro caller's entire budget.
* **Prefer one coherent read.** `GET /account/state` returns the summary and every open position together: cheaper than two calls, and free of the race between them (see [Portfolio & Account State](/api-reference/guides/portfolio.md)).
* **Cache what does not change.** Market metadata from `GET /markets` does not need re-fetching every cycle.
* **Pace from the headers, and from `/account/rate-limit`.** Polling that endpoint is free. Retrying blindly after a `429` is not.

### Related

* [OpenAPI specification](https://github.com/nexus-xyz/nexus-exchange-api) — normative: per-operation weights, classes, and header semantics
* [Networks](/api-reference/guides/networks.md) — how a network is selected, and what binds a key to one
* [Portfolio & Account State](/api-reference/guides/portfolio.md) — the heavy-read surface, and how to read it in one call
* [Rate & Connection Limits](https://docs.nexus.xyz/exchange/apis-and-rates/rate-limits) — HMAC window, token lifetimes, faucet allowance
* [Market Maker Guide](https://docs.nexus.xyz/exchange/apis-and-rates/market-maker-guide) — requesting the MarketMaker tier
* [Interfaces overview](/api-reference/readme.md)

> **Status:** development preview on testnet. Mainnet has not launched. Every ceiling on this page is current configuration rather than a frozen contract — read `/account/rate-limit` in production instead of hardcoding a number, and pin to a released spec version so the per-operation weights you build against stay fixed. Limiter state is not yet durable across gateway restarts. Testnet credentials and balances have no real-world value.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.nexus.xyz/api-reference/guides/rate-limits.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
