# Rate limiting

Several buckets protect the service. They are advertised by response headers so clients can pace themselves before hitting the limit.

## Headers

Every response includes:

```
X-RateLimit-Limit: <quota for the bucket that applied>
X-RateLimit-Remaining: <left in the current window>
X-RateLimit-Reset: <epoch seconds when the bucket refills>
```

Most buckets use a per-minute window; action-specific buckets may use a longer window (e.g. the IGP24 submit bucket refills daily). `X-RateLimit-Reset` always reflects the applicable window.

Exceeding a bucket returns `429` with `Retry-After: <seconds>`.

## Buckets

| Bucket                 | Default quota | When it applies                                              |
| :--------------------- | :------------ | :----------------------------------------------------------- |
| Per-IP auth failure    | 30 / min      | Requests without a valid key. Resets on a successful auth.   |
| Per-account general    | 600 / min     | Authenticated requests, shared across **all keys** under the same account. |
| Per-team IGP24 submit | 5 / day initially; 1000 / day after 5 distinct scoreable pairs | IGP24 `submissions` calls, per active IGP24 team, shared across the competition website and API. |

## Business limits are not rate limits

Some action-specific limits are enforced as business rules, not rate-limit buckets. They return their own error codes rather than `429`:

- `RUN_BUDGET_EXCEEDED` (403) — practice runs exceed your remaining credit balance.

## Error codes

| Code                       | When                                                          |
| :------------------------- | :------------------------------------------------------------ |
| `AUTH_RATE_LIMIT_EXCEEDED` | Per-IP bucket exhausted (no valid key was presented).         |
| `ACCOUNT_RATE_LIMIT_EXCEEDED` | Per-account bucket exhausted.                              |
| `IGP24_SUBMISSION_RATE_LIMIT_EXCEEDED` | Per-team IGP24 submission quota exhausted. |

## Backoff

`Retry-After` is authoritative: when you get a `429`, wait that long before retrying. Cursor pagination controls traversal order, not request rate — it does not by itself keep you under the per-account bucket. For long batches, watch `X-RateLimit-Remaining` and `X-RateLimit-Reset` and pace requests proactively.
