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

# Rate limits

> How much you can call, what happens when you exceed it, and how to stay under.

API-key traffic is rate-limited per key.

<Card title="1,200 requests / 60 seconds" icon="gauge">
  A rolling 60-second window. Your current budget is reported on `GET /api/v1/me` under `rateLimit`.
</Card>

When you exceed the limit, requests return:

```text theme={null}
429 Too Many Requests
```

## Staying under the limit

<Steps>
  <Step title="Stream instead of poll">
    For live prices and chat, use [WebSockets](/websockets/overview). One subscription replaces a
    tight polling loop and doesn't consume your REST budget.
  </Step>

  <Step title="Batch your reads">
    `GET /api/v1/positions` returns perps + spot + predictions in one call; `GET /api/v1/groups/{id}/proposals`
    returns spot + prediction proposals together. Prefer the aggregate over per-vertical loops.
  </Step>

  <Step title="Back off on 429">
    Use exponential backoff with jitter. Don't hammer — repeated 429s waste the window.
  </Step>
</Steps>

## Backoff example

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function withRetry(fn, { tries = 5 } = {}) {
    for (let i = 0; i < tries; i++) {
      const res = await fn();
      if (res.status !== 429) return res;
      const wait = Math.min(2 ** i * 250, 8000) + Math.random() * 250;
      await new Promise(r => setTimeout(r, wait));
    }
    throw new Error("rate limited: gave up after retries");
  }
  ```

  ```python Python theme={null}
  import random, time

  def with_retry(fn, tries=5):
      for i in range(tries):
          res = fn()
          if res.status_code != 429:
              return res
          time.sleep(min(2 ** i * 0.25, 8) + random.random() * 0.25)
      raise RuntimeError("rate limited: gave up after retries")
  ```
</CodeGroup>
