> For the complete documentation index, see [llms.txt](https://series-1.gitbook.io/rundot-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://series-1.gitbook.io/rundot-docs/v5.21.0/readme/rate_limits.md).

# Rate Limits

RUN.game API calls are rate-limited per authenticated user (or per IP for unauthenticated traffic) to protect platform stability. **Most endpoints comfortably support around 60 requests per minute per user.** When you exceed a limit, the server returns HTTP `429 Too Many Requests`.

A few categories are stricter:

* Text generation (`textGen.requestChatCompletionAsync`) and image generation (`imageGen.generate`) are gated separately and may refuse at lower rates. Design these calls assuming they can fail, and keep your game playable when they do.
* Sensitive write operations (e.g. creating comments or accounts) have much tighter caps to prevent abuse.

Build for graceful degradation rather than aiming for exact throughput.

## How errors arrive

Structured SDK failures are `RundotApiError` instances. Each one carries:

| Field    | Type                  | Description                                                                                                  |
| -------- | --------------------- | ------------------------------------------------------------------------------------------------------------ |
| `code`   | `string`              | Machine-readable error code (e.g. `RATE_LIMITED`, `UNKNOWN`). Switch on this instead of parsing the message. |
| `status` | `number`              | HTTP status when the error came over HTTP. `0` for transport/RPC errors that have no HTTP status.            |
| `detail` | `string \| undefined` | Optional human-readable cause from the server (e.g. an upstream provider's validation message).              |

Rate limits surface as `RateLimitedError`, a subclass of `RundotApiError`:

| Field          | Type     | Description                                                                         |
| -------------- | -------- | ----------------------------------------------------------------------------------- |
| `code`         | `string` | Always `'RATE_LIMITED'`.                                                            |
| `status`       | `number` | `429` on the HTTP-backed paths; `0` over the host RPC bridge (see below).           |
| `retryAfterMs` | `number` | Server-suggested backoff in milliseconds. Read this to size your retry delay.       |
| `message`      | `string` | Defaults to `Rate limited. Retry after Ns.` when the server omits a custom message. |

{% hint style="warning" %}
**The HTTP status is not reliable for detection.** In production, `ai`, `textGen`, and `imageGen` calls travel over the host RPC bridge. RPC errors are constructed with `status: 0` regardless of the underlying HTTP status, so matching `429` in the status field (or in the message string) will miss real rate limits. Detect by `code` instead. The typed `RateLimitedError` (with a populated `429` status and `retryAfterMs`) is thrown only by the HTTP-backed `files` and `audioGen` paths. The RPC-backed AI/image paths surface a `RundotApiError` with `code === 'RATE_LIMITED'` and `status === 0`, and the HTTP `threeDGen` path surfaces a `RundotApiError` with `status === 429` but no `retryAfterMs`. Across every transport, `code === 'RATE_LIMITED'` is the reliable signal.
{% endhint %}

{% hint style="info" %}
`RundotApiError` and `RateLimitedError` are not exported from the main package barrel or from the `@series-inc/rundot-game-sdk/api` entrypoint. Rather than relying on `instanceof`, detect rate limits by duck-typing: check `err.code === 'RATE_LIMITED'` (or `err.name === 'RateLimitedError'`). Both signals are reliable across every transport.
{% endhint %}

## Handling 429

Detect a rate limit by checking the error `code`, then back off using `retryAfterMs` when it's present:

```typescript
import RundotGameAPI from '@series-inc/rundot-game-sdk/api'

try {
  const result = await RundotGameAPI.textGen.requestChatCompletionAsync({
    model: 'gpt-5.4-mini',
    messages: [{ role: 'user', content: prompt }],
  })
} catch (err) {
  const apiErr = err as { code?: string; name?: string; retryAfterMs?: number }
  if (apiErr.code === 'RATE_LIMITED' || apiErr.name === 'RateLimitedError') {
    const waitMs = apiErr.retryAfterMs ?? 1000
    // back off for waitMs, then retry, or fall back to a non-AI path
  }
}
```

## Best practices

* On a `RATE_LIMITED` error, back off before retrying.
* Read `RateLimitedError.retryAfterMs` (milliseconds) off the caught error to size your backoff. The SDK consumes the HTTP `Retry-After` header internally; game code never sees raw HTTP headers, so size the wait from `retryAfterMs` instead. When it's absent (e.g. an RPC-path error), fall back to exponential backoff.
* Do not retry tighter than once per second.
* For text generation and image generation, build a non-AI fallback path. Don't gate gameplay on these calls succeeding.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://series-1.gitbook.io/rundot-docs/v5.21.0/readme/rate_limits.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
