Rate Limits
The per-endpoint limits INVO enforces, published from the limits the code applies. This page is the one reference: other pages link here rather than repeating the numbers.
How to read the tables
- • Game means the bucket of the
X-Game-Secret-Keyyou send: every server of yours shares it. - • Player means the
player_emailin the request (or the player the token was minted for, on player-side SDK calls). - • Calling IP means the address your request comes from.
- • Where a row lists several limits, they apply at once, in the order of the "Counted per" column. The first one reached answers
429. - • Paths are production. In sandbox, prefix
/sandbox. Sandbox has smaller capacity than production, so do not load-test it. - • The per-game numbers are sized to sit at or above what the platform itself can process, so a growing game should not need a change. If you expect to approach one, tell us at partners@invo.network.
When you get a 429
Every rate-limit refusal, on every endpoint, has the same shape: HTTP 429 with error_code "RATE_LIMITED", retry_after (seconds) in the body, and a Retry-After header.
The Retry-After header and the body's retry_after are the number of seconds until the window of the limit you hit resets. When more than one limit fired, it is the longest of their resets, so a retry at that moment is not refused by any of them. Wait that long; do not retry sooner. (Before 2026-10-09 an hourly limit always answered 300 seconds, however much of the hour was left.)
HTTP/1.1 429 Too Many Requests
Retry-After: 1742
{
"error": "rate_limit_exceeded",
"error_code": "RATE_LIMITED",
"message": "Request rate limit exceeded. Please slow down and try again.",
"retry_after": 1742,
"limit_type": "rate_limit"
}Match on error_code, not on the message. Some send-safety checks also answer 429 with their own error value (for example an approval or claim that is temporarily locked); they also carry Retry-After. Honour the header whatever the body says.
What your code should do on any 429: wait the number of seconds in Retry-After (or the body's retry_after if the header is not available to you), then retry the same request. Do not hard-code the limits on this page into your client: they can change, and the 429 always tells you how long to wait.
Limits by endpoint
Currency purchase
| Endpoint | Limit | Counted per |
|---|---|---|
POST /api/currency-purchases/quote | 300 / hour; 6,000 / minute | player; caller (120 / minute for a request without X-Game-Secret-Key) |
POST /api/currency-purchases/purchase-currency | 50 / hour; 20,000 / minute | player; game |
POST /api/currency-purchases/confirm-payment | 3,000 / minute; 20 / minute | game; payment |
POST /api/currency-purchases/setup-intent | 20 / hour; 2,000 / minute | player; game |
POST /api/currency-purchases/setup-intent/confirm | 2,000 / minute | game |
GET /api/currency-purchases/player-balance | 6,000 / minute; 100 / minute | game; player |
GET /api/currency-purchases/player-cards | 6,000 / minute; 100 / minute | game; player |
GET /api/currency-purchases/player-purchase-history | 6,000 / minute; 50 / minute | game; player |
GET /api/currency-purchases/order-details | 6,000 / minute | game |
POST /api/checkout/sessions | 3,000 / minute | game |
Item purchase and Platform Commerce
| Endpoint | Limit | Counted per |
|---|---|---|
POST /api/item-purchases/purchase-item | 50 / minute and 200 / hour; 12,000 / minute and 200 / second | player; game |
GET /api/item-purchases/player-purchase-history | 6,000 / minute; 50 / minute | game; player |
GET /api/item-purchases/order-details | 6,000 / minute | game |
POST /api/platform-commerce/purchase | 200 / minute; 50 / minute | calling IP; player |
GET /api/platform-commerce/purchase-status/<order_id> | 300 / minute | calling IP |
GET /api/platform-commerce/purchase-history | 300 / minute | calling IP |
POST /api/platform-commerce/refund | 60 / minute | calling IP |
Sends and transfers
| Endpoint | Limit | Counted per |
|---|---|---|
POST /api/currency-sends/initiate-send | 100 / hour; 120,000 / hour and 3,000 / minute | player (sender); game |
POST /api/currency-sends/verify-sms | 35 / hour; 120,000 / hour | player; game |
POST /api/currency-sends/claim-currency | 25 / hour; 120,000 / hour | player; game |
GET /api/currency-sends/<transaction_id>/status | 6,000 / minute | game |
GET /api/currency-sends/send-history | 6,000 / minute; 30 / minute | game; player |
GET /api/currency-sends/recent-recipients | 6,000 / minute; 30 / minute | game; player |
POST /api/currency-sends/available-destinations | 6,000 / minute | game |
POST /api/transfers/initiate-transfer | 100 / hour; 120,000 / hour and 3,000 / minute | player (sender); game |
POST /api/transfers/verify-sms | 40 / hour; 120,000 / hour | player; game |
POST /api/transfers/claim-transfer | 30 / hour; 120,000 / hour | player; game |
GET /api/transfers/<transaction_id>/status | 6,000 / minute | game |
GET /api/transfers/inbound-pending | 6,000 / minute | game |
GET /api/transfers/transfer-history | 6,000 / minute; 30 / minute | game; player |
GET /api/transfers/recent-recipients | 6,000 / minute; 30 / minute | game; player |
POST /api/transfers/available-destinations | 6,000 / minute | game |
Balances
| Endpoint | Limit | Counted per |
|---|---|---|
GET /api/player-balances/player/by-email/<email> | 2,000 / minute; 100 / minute | game; calling IP |
POST /api/player-balances/batch | 50 / minute; 10 / minute | game; calling IP |
GET /api/player-balances/stream-all | 5 / hour; 2 / hour | game; calling IP |
GET /api/player-balances/game-summary | 100 / minute; 20 / minute | game; calling IP |
SDK, server side (X-Game-Secret-Key)
| Endpoint | Limit | Counted per |
|---|---|---|
POST /api/sdk/player-token | 3,000 / minute | game |
POST /api/sdk/players | 3,000 / minute; 5 / hour | game; player |
POST /api/sdk/players/phone | 3,000 / minute; 5 / hour | game; player |
GET /api/sdk/players/activity | 6,000 / minute; 60 / minute | game; player |
POST /api/sdk/players/phone/change-requests | 3,000 / minute; 5 / hour | game; player |
GET /api/sdk/players/phone/change-requests/<id> | 6,000 / minute | game |
SDK, player side (player token, called from the player's device)
| Endpoint | Limit | Counted per |
|---|---|---|
POST /api/sdk/transfers/<id>/approve, POST /api/sdk/send/<id>/approve | 12 / minute | player |
POST /api/sdk/transfers/<id>/confirm-receipt, POST /api/sdk/send/<id>/confirm-receipt | 12 / minute | player |
POST /api/sdk/players/phone/change-requests/<id>/approve | 12 / minute | player |
GET /api/sdk/players/phone/change-approvals | 120 / minute | player |
POST /api/sdk/approvals/device/begin | 10 / minute | player |
POST /api/sdk/approvals/device/poll | 40 / minute (honour the interval in the response) | player |
GET /api/sdk/transfers/pending, GET /api/sdk/balance, GET /api/sdk/destinations | 120 / minute | calling IP (the device) |
Not every refusal is a rate limit. Money flows also have per-player business limits (amounts, counts and velocity) that answer with their own codes, listed on each flow's page and in the error reference. Additional protections exist that are not listed here and may answer 429 to abusive traffic.
On any 429, from any endpoint, back off using Retry-After.