Rate limits
This page describes the rate limits Open Play applies today and what to do when you get a 429. An important honesty note: there is no general rate limit per API key. Limits are placed on specific operations (sign-in, sign-up, chat, plugin command, socket connection); the other endpoints are unlimited and you must manage the load.
What you need to know
- There is no general “N requests per minute” quota. For calls made with a server key, there is no general limit per key or per environment.
- Counters are kept in Redis and if Redis cannot be reached, the limit is not applied (fail-open); the only exception is the audit-row ceiling noted below. Do not use the limit alone as a security control.
- The
Retry-Afterheader exists only on the plugin429(60 s) and on503responses (30 s). The429of the core and identity endpoints carries noRetry-After; apply your own backoff. - The client IP is the key of the limits on the identity endpoints. If the reverse proxy does not forward the client IP, all players share the same bucket (production checklist).
Identity endpoints (the player sign-in line)
These limits are applied in the service layer; they are the same in REST itself. The window is fixed (it starts with the first request and the counter resets when the time expires; it is not a sliding window); an overage returns 429 and the plain text rate limited (no Retry-After).
| Bucket | Limit | Window | Key | Which endpoints |
|---|---|---|---|---|
| Opening a guest | 30 | 60 s | Client IP | POST /v1/auth/sign-in/guest (re-sign-in counts too) |
| Email sign-in | 5 | 60 s | Client IP | POST /v1/auth/sign-in/email (every attempt) |
| Failed sign-in lock | 10 | 900 s | Address + IP | Wrong password attempts; the allowance is refunded on a successful sign-in; a wrong current password on a password change counts too |
| Sign-up (IP) | 10 | 60 s | Client IP | POST /v1/auth/sign-up/email, guest upgrade |
| Sign-up (session) | 30 | 60 s | Session owner | POST /v1/auth/guest/upgrade/email |
| Sign-up (address) | 5 | 900 s | Email address | Sign-up and upgrade |
| Password change (IP) | 10 | 60 s | Client IP | POST /v1/auth/change-password |
| Password change (account) | 6 | 60 s | Player | POST /v1/auth/change-password |
| Recovery (IP) | 5 | 60 s | Client IP | forgot-password, reset-password, sign-in/magic-link (the three endpoints share one bucket) |
| Recovery (address) | 5 | 900 s | Email address | forgot-password, sign-in/magic-link |
| OAuth start | 30 | 60 s | Client IP | sign-in/oauth/{provider}, link/{provider} |
| OAuth return | 60 | 60 s | Client IP | callback/{provider} |
In addition:
| Limit | Limit | Key | Note |
|---|---|---|---|
| Display name change | 10 / 3600 s | Player | POST /v1/auth/session/display-name; an overage is 429 |
| Account deletion attempt | 5 / 3600 s | Player | POST /v1/auth/account/delete; an overage is 429 |
| Failed sign-in audit row | 20 / 3600 s | Account | Does not reject the sign-in; it only throttles the background audit record. During a Redis outage the record is skipped (fail-closed) |
Note
The guest opening bucket is per IP. If the reverse proxy does not forward the client IP, or many players connect from behind a NAT/dormitory network, the limit of 30 guests/minute fills quickly. If you plan a launch or a load test, verify IP forwarding.
Chat and notifications
| Limit | Limit | Window | Key | When exceeded |
|---|---|---|---|---|
| Sending a chat message | 20 | 10 s | Environment + player | 429 in REST; message_rejected on the chat socket |
| Notification delivery | 60 | 60 s | Environment + player | Delivery is deferred (the provider is not hit), no error returns to the player |
Plugin commands
Plugin commands and queries can be subject to per-minute budgets per player, per bucket. The plugin defines the budget; the server applies it with the same decision on the REST, realtime command, and gRPC paths. An overage arrives with the plugin envelope: 429, code: "rate_limited", retryable: true, Retry-After: 60.
The plugin that defines a budget today is package-ride: 6 to 60 commands per bucket in a 60 s window (for example delivery accept/start/complete 12, login bonus 10, quest reward and garage 30, collision/race bet 60). The bucket values are plugin-specific and may change; for exact numbers, see the plugin’s documentation. The operator can put some buckets into “observation” mode; in this mode an overage is only counted, and the command is not rejected.
In the Unity SDK, PluginClient.RetryAfter(error) returns 60 seconds for the plugin 429.
Realtime socket
| Limit | Value | When exceeded |
|---|---|---|
| New connections per address | 30 / minute | The upgrade is rejected |
| Open sockets per address | 8 | CapacityExceeded, the socket closes |
| Open sockets per player | 4 | CapacityExceeded, the socket closes |
| Per-connection frame rate | 40/s (a 160-frame burst allowance) | CapacityExceeded (retryable: true), the socket closes |
Details: reconnect and limits.
When you get a 429
HTTP/1.1 429 Too Many Requests
Content-Type: text/plain; charset=utf-8
rate limited
On a plugin endpoint:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
x-request-id: 0192a3b4-0000-7000-8000-0000000000d1
{"code":"rate_limited","message":"rate limited","retryable":true,"requestId":"0192a3b4-0000-7000-8000-0000000000d1"}
- If there is a
Retry-After, wait that long; if not, use exponential backoff (for example 1, 2, 4, 8 s; upper bound 30 s) and add a random jitter. - If the request is modifying, repeat with the same
Idempotency-Key(idempotency and concurrency). - For limits triggered by a user action (sign-in, sign-up), instead of an automatic retry show the user the wait time; on the password and sign-up endpoints an automatic retry extends the lock time.
CrxClient(server key) retries a429onGETand keyed requests itself, respectingRetry-After(default 2 attempts).
Warning
A rule budget overage (
rule budget exceeded: <rule>) also returns429; this is not a rate limit but the rule engine budget (50 rules per command, 200 actions, 500 evaluation nodes, and chain depth 5). Retrying does not change the outcome; simplify the rule (game rules).
What you need to do on your side
Because there is no general server limit, you need to add protection under heavy load:
- In server-to-server bulk jobs, limit concurrency (for example 5 to 10 requests at a time) and manage errors with backoff.
- Measure the request rate per key on your own side; a single leaked key can make unlimited calls (security).
- Before a launch, cache
GET /v1/…reads (catalog endpoints support conditional reads withETagandIf-None-Match; do not poll per player more often than every 15 s).