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

# Rate Limits

> Per-key quotas, X-RateLimit-* response headers, and how to handle 429s.

The REST API enforces a per-key sliding-window quota. When a key exceeds it, the next request returns `429 RATE_LIMIT_EXCEEDED` with `Retry-After` and `X-RateLimit-*` headers so you can back off cleanly.

## Per-tier defaults

| Tier         | Rate limit (req/min) | Daily spend cap | Per-trade cap | High-value confirm gate |
| ------------ | -------------------- | --------------- | ------------- | ----------------------- |
| `free`       | 60                   | 1,000 USDC      | 500 USDC      | none                    |
| `developer`  | 300                  | 10,000 USDC     | 2,500 USDC    | none                    |
| `enterprise` | Custom (admin-set)   | 100,000 USDC    | 25,000 USDC   | 25,000 USDC             |
| `mm`         | Custom (admin-set)   | 1,000,000 USDC  | 50,000 USDC   | 50,000 USDC             |

A tier sets four independent limits:

* **Rate limit** — HTTP requests per minute, sliding window. Hit this and you get `429 RATE_LIMIT_EXCEEDED`.
* **Daily spend cap** — rolling 24h aggregate USDC volume per key. Hit this and you get `409 SPENDING_LIMIT_EXCEEDED` with `extensions.cap = "daily_volume"`. Sells and close-position intents do NOT draw against this cap.
* **Per-trade cap** — single-trade USDC amount. Hit this and you get `409 SPENDING_LIMIT_EXCEEDED` with `extensions.cap = "per_trade"`.
* **High-value confirm gate** — for tiers that have one, any trade ≥ this amount returns `202 Accepted` with a `confirmationToken`; you then `POST /v1/trades/{id}/confirm` to actually execute. Two-step flow lets you catch a runaway bot before it commits a six-figure trade.

The rate-limit window is **per `user_id`**, not per individual key. If a single user holds two keys, HTTP requests across both share the quota — so issuing extra keys doesn't multiply your throughput. The **spending caps**, however, are **per key** — each key gets its own daily allotment.

Each user can hold at most **5 active keys** at a time (revoked keys don't count). This guards against quota-pooling abuse patterns.

## Why these caps exist

The caps are not punitive. Five concrete reasons:

1. **Blast radius on key compromise.** API keys leak — committed to git, dumped in error reports, laptop stolen. The cap bounds the loss between leak-time and revoke-time.
2. **Runaway-bot circuit breaker.** A trading loop misfires (off-by-one in your sizing, retry storm on a timeout, missing dedup on a webhook). The cap is a hard stop you cannot disable from inside your own code.
3. **AMM stability.** Kash markets use a Pythagorean AMM with thin per-outcome liquidity. A trade consuming more than \~5% of available liquidity moves the price substantially. Per-trade caps protect the AMM curve for everyone.
4. **Compliance ladder.** Higher daily volumes implicitly require enhanced KYC, ToS attestation, and sanctions screening. The tier names map to that ladder.
5. **Delegation guardrails.** API keys are a scoped delegation against the user's Privy-managed smart account — Kash never holds the keys or the funds, but for the duration of the delegation a leaked key (or a runaway bot) can move balance up to whatever the smart account holds. Caps bound that blast radius. The user can revoke any key at any time; the cap is the runtime safety net while it's active.

If you want a surface with no caps, the self-orchestrated track (`@kashdao/protocol-sdk`) is the right product — you sign and submit every transaction yourself with your own signer/RPC/bundler, and bear the full operational risk. Both paths are non-custodial; this one just removes the platform-side guardrails along with the platform-side orchestration.

## Response headers (every response)

Every response — `200`, `4xx`, `429` — carries the live state of your bucket:

```
X-RateLimit-Limit:     300
X-RateLimit-Remaining: 287
X-RateLimit-Reset:     1730000060   # Unix epoch seconds; when remaining refills
```

* `X-RateLimit-Limit` — your configured quota for the window.
* `X-RateLimit-Remaining` — how many requests you can still send before the window resets.
* `X-RateLimit-Reset` — Unix epoch seconds when `Remaining` refills back to `Limit`.

Use these to pace yourself proactively rather than waiting for a `429`.

## Hitting the limit

When you exceed the quota:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 38
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1730000098

{
  "type": "https://docs.kash.bot/developer-docs/api-errors/RATE_LIMIT_EXCEEDED",
  "title": "Rate limit exceeded",
  "status": 429,
  "code": "RATE_LIMIT_EXCEEDED",
  "detail": "API key has exceeded its 300 req/min quota.",
  "requestId": "req_..."
}
```

**Always** respect `Retry-After` (seconds). The TypeScript SDK does this automatically with exponential backoff and jitter.

## Algorithm

The rate limiter uses a **sliding window** for read paths (markets, portfolio, account, traces, webhooks list) and a **token bucket** for write paths (`POST /v1/trades`, `POST /v1/trades/{id}/confirm`).

* **Sliding window** — smooth, no burst. Best for sustained polling.
* **Token bucket** — allows short bursts (capacity = limit; refill = limit/60 per second). Best for human-driven actions.

Both algorithms are keyed on `user:{userId}`.

## Per-route rate limit policies

Most endpoints share the per-key tier quota. A few are tighter:

| Endpoint pattern                                  | Algorithm      | Notes                                                    |
| ------------------------------------------------- | -------------- | -------------------------------------------------------- |
| `GET /v1/markets*`, `GET /v1/portfolio*`          | sliding-window | Standard tier quota.                                     |
| `GET /v1/markets/{id}/quote`                      | sliding-window | Standard tier quota; backed by RPC.                      |
| `POST /v1/trades`, `POST /v1/trades/{id}/confirm` | token-bucket   | Tighter — write paths cost more.                         |
| `POST /v1/webhooks/events/{id}/redeliver`         | token-bucket   | Replay amplification cap (max 5 redeliveries per event). |

## Best practices

* **Read your headers.** Cheaper than retry-after-rejection. Aim to keep `X-RateLimit-Remaining` above 10% of `X-RateLimit-Limit` under steady-state load.
* **Use ETags on read paths.** A `304 Not Modified` doesn't count toward your quota — see **[ETags](/developer-docs/rest-api/etags)**.
* **Use the SDK.** It handles `Retry-After`, exponential backoff, jitter, and idempotency-safe retries automatically.
* **Distribute across keys carefully.** Rate-limit quota is per-`user_id`, so multiple keys for the same user don't help with HTTP throughput. (Spending caps ARE per-key.) If you need higher throughput, the upgrade path is below.
* **Don't hammer the quote endpoint.** Quotes are RPC-heavy. Cache results client-side for the freshness window your strategy can tolerate (typically 5–15s).

## Kill switches

Independent from rate limiting, every route has an ops-controlled kill switch. When triggered (rare — usually during chain RPC degradation), the route returns:

```http theme={null}
503 Service Unavailable
Content-Type: application/problem+json

{
  "code": "ROUTE_DISABLED",
  "title": "Route temporarily disabled",
  "detail": "This endpoint is currently disabled by ops. Try again later."
}
```

Treat `503 ROUTE_DISABLED` like `Retry-After` — back off and retry. Most kill switches are toggled off within minutes.

## How to upgrade your tier

Hitting `RATE_LIMIT_EXCEEDED` or `SPENDING_LIMIT_EXCEEDED` repeatedly during normal operation means you've outgrown your tier — that's the signal to upgrade. Today, every tier above `free` is provisioned manually:

* **`free` → `developer`** (5× rate limit, 10× spending caps): email **[support@kash.bot](mailto:support@kash.bot)** with your account email and a one-line description of your use case. Self-serve via Stripe is on the roadmap; until it ships we provision developer-tier keys on a case-by-case basis.
* **Anywhere → `enterprise`** ($100k/day, $25k/trade, custom rate limit): **[contact sales](mailto:sales@kash.bot)**. Custom pricing based on volume commitment.
* **Anywhere → `mm`** ($1M/day, $50k/trade, full counterparty terms): **[contact partnerships](mailto:partnerships@kash.bot)**. KYB, ToS attestation, and possibly escrow arrangements — this is a relationship, not a pricing tier.

`enterprise` and `mm` tiers can override any of the four axes per-key as part of the contract — a key can have a different `daily_spend_limit_usdc`, `per_trade_limit_usdc`, or `high_value_trade_threshold_usdc` than the tier default.

## Telemetry

Your per-key telemetry — including 24h `rateLimitRejections` count — is exposed via `GET /v1/account/usage`. If you're seeing unexpected `429`s, that's the first place to check.

```bash theme={null}
curl https://api.kash.bot/v1/account/usage \
  -H "X-API-Key: $KASH_API_KEY" | jq '.data.auth["24h"].rateLimitRejections'
```

## Next

<CardGroup cols={2}>
  <Card title="Errors" icon="circle-exclamation" href="/developer-docs/rest-api/errors">
    The full RFC 7807 error format and code catalog.
  </Card>

  <Card title="ETags" icon="bolt" href="/developer-docs/rest-api/etags">
    `If-None-Match` short-circuits — free quota for unchanged data.
  </Card>
</CardGroup>
