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

# Relay Rate Limits and Migration

> Account-scoped Relay login, configured request limits, and the November 4 migration.

## Scheduled rate-limit migration

> [!WARNING]
>
> Action for trading integrations: obtain a verified `uid`-scoped JWT for each account you trade before the November 4 cutover. At cutover, **all wallet-only JWTs—including those issued earlier—immediately move to the 1 request/second sustained allowance**. There is no grace period or grandfathering. Test the scoped flow on the same network as your trading account.

Relay is scheduled to complete its account-scoped rate-limit migration on **Wednesday, November 4, 2026 at 12:00 PM Eastern Time (EST, UTC−05:00; 17:00 UTC)**. At that time, requests without a verified account-scoped token will use a **1 request/second sustained per-wallet allowance**, including requests made with wallet-only JWTs issued before cutover. There is **no grace period or exception for existing tokens**. Those tokens remain valid, but their requests use the lower quota because Relay applies the limit when it processes each request, not when it issues the JWT. The new sustained rate is a scheduled change, **not a claim that the lower rate is already active**. The post-cutover burst allowance has not been announced; do not assume an instantaneous one-request ceiling.

For account-scoped trading, the mainnet and testnet configuration currently specifies these *per-replica defaults*:

| Quota | Sustained refill | Burst capacity | Scope |
| - | -: | -: | - |
| Verified root account | 250 requests/second | 250 requests | All signers and subaccounts of one root, across markets |
| Verified root account + market | 250 requests/second | 250 requests | All signers and subaccounts of one root trading one market |
| Wallet-only, current configuration | 250 requests/second | 250 requests | One wallet on the wallet-based path |
| Unscoped wallet after cutover | 1 request/second | To be announced | Wallet and headerless operations |

Both verified-account buckets must allow a trading submission. A submission costs **one request unit, not one per order** in a batch. A request admitted to the limiter still counts if a later policy check or simulation rejects it. Subaccounts share their root's budgets; separate signer keys, WebSocket connections, REST calls, or JWT renewals do not create extra root allowance within one replica.

These are not guaranteed end-to-end throughput figures. Existing limits also apply, including **100 requests/second per IP**, **200 requests/second per identity**, **500 WebSocket messages/second**, and a **100-request per-method burst**. An additional shared wallet-tier bucket is configured at 1,000 requests/second with a 1,000-request burst; it does not replace the per-wallet limit. Account-specific policy overrides may change an effective allowance. Current buckets are in-memory **per Relay replica**, not a globally distributed hard quota. Limits are enforced server-side and can change for existing tokens and connections without JWT renewal. Plan to handle throttling, not to multiply connections to evade it.

“Requests/second” is the sustained token refill rate; burst is a separate number of requests that can be admitted in a short interval when credit is available. A `RATE_LIMITED` response is ordinary throttling, not an outage.

## Move to account-scoped login

1. Confirm the trading wallet has live AccountCore `TRADE` authority for the exact account ID it will trade. For a child account, use that child's ID.
2. Call the unchanged public `POST /auth/challenge` for the wallet and sign the exact returned message.
3. Exchange the signed challenge at public `POST /auth/token` with an additional `uid` **string**, for example `"10"`. It must be a positive canonical uint40 decimal value, without leading zeroes or whitespace. [Token exchange](https://docs.kuru.io/api/relay/reference/exchange-token.md) shows the request.
4. Store the new JWT securely and use it for that account. Re-login with `uid` for each traded account/subaccount: adding an account ID to a submission cannot scope an already-issued unscoped JWT. Set the signed trading header's `accountId` to the token's exact `uid`. For WebSocket, also set the required upgrade query `account_id` to that same ID.
5. Exercise the same signed submissions on testnet, handle `RATE_LIMITED` with bounded retry, and reconcile broadcasts or uncertain results before submitting another logical action.

Relay verifies the challenge wallet's current on-chain trading authorization for `uid` and resolves its root when issuing the token. A successful scoped JWT has a signed `kuru_account` claim with `uid`, `root_uid`, `chain_id`, and `account_core` string fields. These are Relay-issued facts; clients cannot self-assert a root ID or gain a higher allowance by merely changing a request body. The token response envelope remains `accessToken`, `tokenType`, `expiresAt`, and `wallet`; it does not return the quota or root as separate response fields. Token issuance neither registers an account nor grants on-chain permission. Renewal rechecks trading permission; contracts still enforce authorization at execution. Trading admission does not add an authorization RPC for each submission.

Methods without a trading `header`—including signer onboarding/revocation, subaccount creation, and stored-trigger or TWAP cancellation/finalization—use the wallet-based quota **even with a scoped JWT**. For AccountCore operations, the signed owner/administrator `authorizer` remains independent of the JWT wallet. Do not put `uid` on `POST /auth/challenge`, rename signed `accountId`, or replace WebSocket `account_id` with `uid`.

The pinned `@toxicflow-labs/ts-sdk@0.2.0` Relay `authenticate()`/`exchangeToken()` methods do not send `uid`. Until an SDK release supports scoped login, perform the two public authentication calls directly and supply the resulting token to the SDK client's `accessToken`/`setAccessToken` option, or use the documented REST/WebSocket requests directly. See [Relay SDK authentication](https://docs.kuru.io/sdk/relay.md#authenticate).

## When Relay throttles a request

REST returns HTTP **429** with `code: "RATE_LIMITED"`, `retryable: true`, and `retryAfterMs`. The equivalent WebSocket request is rejected without closing the connection solely because of the new account limiter. Honor the reported delay, use bounded backoff with jitter, and pace future requests. A retried WebSocket submission needs a **new envelope `id`**, because replaying the same `id` can replay the old rejection. The envelope `id` is not a signed intent nonce, `clientOrderId`, or durable idempotency key; retain the original signed identity and reconcile state before retrying. Never blindly resubmit a `BROADCAST` or an uncertain result. Outcome pushes and `transaction.subscribe` do not consume the transaction quota, though WebSocket frame limits still apply. See [Relay errors and outcomes](https://docs.kuru.io/api/relay/errors.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.