# Rate limits

> Each key may make 120 calls a minute. Past that, calls answer 429 with a Retry-After header that says how long to wait.

Markdown twin of https://panlytics.net/developers/security/rate-limits (last updated 2026-10-25). Every page of the Panlytics developer docs is listed at https://panlytics.net/developers/llms.txt, and all of them are in one file at https://panlytics.net/developers/llms-full.txt. The site guide is https://panlytics.net/llms.txt.

## The limit

Each key may make **120 calls a minute**. The minute is counted per key, not per person or per company, so two keys made by the same person each have their own 120.

Only calls with a valid key count. Somebody who knows part of your key but not its secret cannot use up your minute.

## When you go over

The call that goes past the limit, and every call after it until the minute turns, answers `429` with the code `api.rate_limited` and a `Retry-After` header:

```http title="HTTP"
HTTP/1.1 429 Too Many Requests
Retry-After: 23
Content-Type: application/json

{
  "code": "api.rate_limited",
  "error": "This key made too many calls in one minute.",
  "requestId": null,
  "detail": "23 seconds"
}
```

`Retry-After` is the number of seconds until the minute turns. Wait that long, then call again. Do not retry sooner: every early retry is refused the same way.

## Handling it in code

```python title="Python"
import os
import time
import requests

KEY = os.environ["PANLYTICS_KEY"]

def call(url: str) -> dict:
    for _ in range(3):
        answer = requests.get(url, headers={"Authorization": f"Bearer {KEY}"}, timeout=30)
        if answer.status_code != 429:
            answer.raise_for_status()
            return answer.json()
        time.sleep(int(answer.headers.get("Retry-After", "60")))
    raise RuntimeError("Still rate limited after three waits.")
```
```typescript title="TypeScript"
async function call(url: string): Promise<unknown> {
  for (let i = 0; i < 3; i++) {
    const answer = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PANLYTICS_KEY}` } });
    if (answer.status !== 429) {
      if (!answer.ok) throw new Error(`${answer.status}: ${(await answer.json()).code}`);
      return answer.json();
    }
    const wait = Number(answer.headers.get("Retry-After") ?? "60");
    await new Promise((done) => setTimeout(done, wait * 1000));
  }
  throw new Error("Still rate limited after three waits.");
}
```

## Other limits

| Limit | Value |
| --- | --- |
| Calls per key | 120 a minute |
| Wrong keys from one address | 30 a minute, then `429` from that address |
| Chat runs | 30 an hour per person, the same as in the app, then `429 net.rate_limited` |
| Live streams per key | 3 at once, then `429 net.rate_limited` with `Retry-After: 15` until one closes |
| AI sign-in links | 6 an hour per key maker, then `429 agent.links_limit` with `Retry-After` |

Chat also spends the shop's daily AI budget: 2 euros a day per shop unless the CEO or a Manager changes it in the organisation's AI settings. When a shop's budget for the day is used up, chat answers `429` with the code `api.ai_budget` and a `Retry-After` header holding the seconds until midnight UTC, when the day turns.
