# Rate limits

> Your request budget, the headers that report it, and how to back off cleanly.


The API is limited per minute, per account. The default is **120 requests per minute**.

## How the budget is shared

The budget belongs to your **account**, not to a key. Three keys on one account draw from the same 120, so minting more keys does not buy more throughput.

Two things can change your limit:

- **A per-key limit.** Set one when creating a key to stop a single noisy integration eating everyone else's budget. It only ever lowers, never raises.
- **Your account limit.** Ask support if you need more. An admin can raise it for your account, and it then applies to every key you hold.

`GET /api/v1/me` reports the limit that applies to the key you called with:

```json
{
    "api_key": {
        "rate_limit_per_minute": 120
    }
}
```

## Headers

Every response carries your current position in the window.

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed per minute |
| `X-RateLimit-Remaining` | Requests left in this window |
| `Retry-After` | Seconds until the window resets. Only sent on a `429` |

Read `X-RateLimit-Remaining` as you go and slow down before you hit zero. It is cheaper than being refused.

## When you are limited

```json
{
    "message": "Rate limit exceeded.",
    "code": "rate_limit_exceeded",
    "retry_after": 34
}
```

The response is `429` and carries `Retry-After`. Wait that long, then continue. Retrying immediately just burns budget you do not have.

```python tab=Python
import time
import requests

def call(path):
    while True:
        response = requests.get(
            f"https://dashboard.steamlabs.dev/api/v1{path}",
            headers={"Authorization": f"Bearer {api_key}"},
        )

        if response.status_code != 429:
            return response

        time.sleep(int(response.headers.get("Retry-After", 5)))
```

```javascript tab=Node
async function call(path) {
    while (true) {
        const response = await fetch(`https://dashboard.steamlabs.dev/api/v1${path}`, {
            headers: { Authorization: `Bearer ${apiKey}` },
        });

        if (response.status !== 429) {
            return response;
        }

        const wait = Number(response.headers.get('Retry-After') ?? 5);
        await new Promise((resolve) => setTimeout(resolve, wait * 1000));
    }
}
```

```php tab=PHP
$response = Http::withToken($apiKey)
    ->retry(5, throw: false, when: fn ($e, $response) => $response?->status() === 429)
    ->get('https://dashboard.steamlabs.dev/api/v1/accounts');
```

## Staying inside the budget

Most clients that hit the limit are asking for one thing at a time when they could ask for many.

- **Raise `per_page`.** One request for 200 accounts beats 200 requests for one. See [Pagination and filtering](/docs/api/en/concepts/pagination-and-filtering).
- **Use bulk endpoints.** Queueing one task for 5,000 accounts is a single call. See [Bulk operations](/docs/api/en/concepts/bulk-operations).
- **Filter server-side.** Ask for the accounts you want rather than fetching everything and filtering locally.
- **Cache what does not move.** Your plan limits, tags, and proxy groups change rarely.

> [!TIP]
> Polling a task's status every second for ten minutes is 600 requests. Poll every ten seconds instead and you have spent 60, with no meaningful loss of freshness.

## The pre-authentication limit

There is a second limit in front of authentication, and you will not meet it with a working key. It exists so that someone spraying invalid keys is refused before the request costs anything.

It counts two things a working client does not do: requests presenting one token far faster than any account budget allows, and **failed** authentications from one address. Successful calls never count towards the second one however many you make, so a busy integration cannot exhaust it, and neither can another account sharing your egress address.

The one way to meet it honestly is a key that has stopped working (revoked, expired, or mistyped) behind a client that retries in a tight loop. You get the same `429 rate_limit_exceeded` as above. Fix the key rather than retrying it: a credential being refused will not start working on the next attempt.
