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-Key you send: every server of yours shares it.
  • • Player means the player_email in 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

EndpointLimitCounted per
POST /api/currency-purchases/quote300 / hour; 6,000 / minuteplayer; caller (120 / minute for a request without X-Game-Secret-Key)
POST /api/currency-purchases/purchase-currency50 / hour; 20,000 / minuteplayer; game
POST /api/currency-purchases/confirm-payment3,000 / minute; 20 / minutegame; payment
POST /api/currency-purchases/setup-intent20 / hour; 2,000 / minuteplayer; game
POST /api/currency-purchases/setup-intent/confirm2,000 / minutegame
GET /api/currency-purchases/player-balance6,000 / minute; 100 / minutegame; player
GET /api/currency-purchases/player-cards6,000 / minute; 100 / minutegame; player
GET /api/currency-purchases/player-purchase-history6,000 / minute; 50 / minutegame; player
GET /api/currency-purchases/order-details6,000 / minutegame
POST /api/checkout/sessions3,000 / minutegame

Item purchase and Platform Commerce

EndpointLimitCounted per
POST /api/item-purchases/purchase-item50 / minute and 200 / hour; 12,000 / minute and 200 / secondplayer; game
GET /api/item-purchases/player-purchase-history6,000 / minute; 50 / minutegame; player
GET /api/item-purchases/order-details6,000 / minutegame
POST /api/platform-commerce/purchase200 / minute; 50 / minutecalling IP; player
GET /api/platform-commerce/purchase-status/<order_id>300 / minutecalling IP
GET /api/platform-commerce/purchase-history300 / minutecalling IP
POST /api/platform-commerce/refund60 / minutecalling IP

Sends and transfers

EndpointLimitCounted per
POST /api/currency-sends/initiate-send100 / hour; 120,000 / hour and 3,000 / minuteplayer (sender); game
POST /api/currency-sends/verify-sms35 / hour; 120,000 / hourplayer; game
POST /api/currency-sends/claim-currency25 / hour; 120,000 / hourplayer; game
GET /api/currency-sends/<transaction_id>/status6,000 / minutegame
GET /api/currency-sends/send-history6,000 / minute; 30 / minutegame; player
GET /api/currency-sends/recent-recipients6,000 / minute; 30 / minutegame; player
POST /api/currency-sends/available-destinations6,000 / minutegame
POST /api/transfers/initiate-transfer100 / hour; 120,000 / hour and 3,000 / minuteplayer (sender); game
POST /api/transfers/verify-sms40 / hour; 120,000 / hourplayer; game
POST /api/transfers/claim-transfer30 / hour; 120,000 / hourplayer; game
GET /api/transfers/<transaction_id>/status6,000 / minutegame
GET /api/transfers/inbound-pending6,000 / minutegame
GET /api/transfers/transfer-history6,000 / minute; 30 / minutegame; player
GET /api/transfers/recent-recipients6,000 / minute; 30 / minutegame; player
POST /api/transfers/available-destinations6,000 / minutegame

Balances

EndpointLimitCounted per
GET /api/player-balances/player/by-email/<email>2,000 / minute; 100 / minutegame; calling IP
POST /api/player-balances/batch50 / minute; 10 / minutegame; calling IP
GET /api/player-balances/stream-all5 / hour; 2 / hourgame; calling IP
GET /api/player-balances/game-summary100 / minute; 20 / minutegame; calling IP

SDK, server side (X-Game-Secret-Key)

EndpointLimitCounted per
POST /api/sdk/player-token3,000 / minutegame
POST /api/sdk/players3,000 / minute; 5 / hourgame; player
POST /api/sdk/players/phone3,000 / minute; 5 / hourgame; player
GET /api/sdk/players/activity6,000 / minute; 60 / minutegame; player
POST /api/sdk/players/phone/change-requests3,000 / minute; 5 / hourgame; player
GET /api/sdk/players/phone/change-requests/<id>6,000 / minutegame

SDK, player side (player token, called from the player's device)

EndpointLimitCounted per
POST /api/sdk/transfers/<id>/approve, POST /api/sdk/send/<id>/approve12 / minuteplayer
POST /api/sdk/transfers/<id>/confirm-receipt, POST /api/sdk/send/<id>/confirm-receipt12 / minuteplayer
POST /api/sdk/players/phone/change-requests/<id>/approve12 / minuteplayer
GET /api/sdk/players/phone/change-approvals120 / minuteplayer
POST /api/sdk/approvals/device/begin10 / minuteplayer
POST /api/sdk/approvals/device/poll40 / minute (honour the interval in the response)player
GET /api/sdk/transfers/pending, GET /api/sdk/balance, GET /api/sdk/destinations120 / minutecalling 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.