Skip to main content
The Game API is fast, but every call is a network request that shares a per-server budget. A few habits keep your experience responsive and prevent your calls from being throttled or dropped. Read this once before shipping.

Respect the rate limits

Every operation — single calls, batched calls, and reads — draws from one per-server budget: When you exceed the budget, calls start returning nil/false (HTTP 429). Some expensive methods also carry their own, tighter limit — for example getLeaderboard allows one call every 30 seconds per server. These are noted on the relevant method.
Budgets are per game server, not per player. In a full 50-player server, “one call per player” can blow the entire minute budget in seconds. Always think in terms of the whole server.

Don’t call the same endpoint repeatedly

The single biggest cause of throttling is re-fetching data that hasn’t changed. Fetch it once, keep the result, and reuse it.
Never put an API call inside a loop that runs every frame or tick — a RunService.Heartbeat/Stepped/RenderStepped connection, a while true loop, or a per-hit combat handler. Even a “cheap” read will exhaust the budget and stall the calling thread (every call yields).
Fetch on the event that actually changes the data instead:

Cache reads in your own code

For data you read often, keep a small in-memory cache keyed by user id and refresh it only when it matters (on a timer, or after you change the underlying data).
A few methods are already cached for you per player for the length of their session — getUserPermissions and getUserRoles. You still shouldn’t call them in a loop, but you don’t need to build your own cache for them.
If you need to perform several operations at once, send them together with apiBatch instead of firing many separate calls. One batch of 20 counts the same against the budget as 20 singles, but it’s a single round-trip — faster, and far less likely to interleave with other work.
Award end-of-session points, mark attendees, and write logs in a single batch at the end rather than one call per player as things happen.

Debounce player-triggered actions

Any call a player can trigger (a button, a command, a chat trigger) needs a guard, or a single player can spam your budget. Track a per-player timestamp and reject calls that come too fast.

Handle failures without hammering

A nil or false return can mean a rejection or a transient network issue. Always check the result before using it, and if you retry, back off — never retry in a tight loop.
Do not retry immediately or in a loop. If you were throttled, retrying instantly keeps you throttled and can starve every other call in the server.

Run independent calls concurrently — within reason

Because each call yields, running several independent calls sequentially adds up their wait times. Use task.spawn to overlap them — but don’t fan out hundreds at once, or you’ll hit the budget instantly.
For a value you need for many users at once, prefer a method that returns them together (e.g. getLeaderboard with a userIds list) over one call per user.

Keep payloads small and reuse identifiers

  • Each operation’s args payload is capped at 24 KB. Send only what the method needs — don’t forward large tables or entire data models.
  • When you start a session, keep the returned sid and pass it to every follow-up call rather than fetching the session again to find it.
  • Remember timestamps are Unix epoch milliseconds (see Core Concepts → Timestamps); os.time() returns seconds, so multiply by 1000.

Quick checklist

No API calls inside per-frame or while true loops.
Reads that don’t change are cached (or fetched once on join).
Multiple operations are grouped into a single apiBatch.
Player-triggered actions are debounced.
Every result is checked for nil/false before use.
Retries use backoff, never a tight loop.

Next steps

Core Concepts

Actor object, return conventions, yielding, and the full rate-limit rules.

apiBatch

Run several operations in a single request.