Getting started
Rate limits
How requests are budgeted, which headers report the budget, and how to back off.
No numbers on this page are real. The limits, the window, and the header names are all undecided. This page documents the intended mechanism so client code can be structured correctly, not the values to code against.
The bucketing problem
TODO: the current limiter keys purely on IP, which does not work for Roblox. Roblox game servers egress through a shared pool of Roblox-owned addresses, so IP-based limiting would put every experience on the platform into one bucket — your traffic would be throttled by strangers, and a busy neighbour could lock you out entirely.
Per-key bucketing has to land before the public API launches. Options under discussion:
- Per key
- Per key, per clan
- Per key with a separate, higher ceiling for reads
Limits
| Scope | Limit | Window |
|---|---|---|
| Reads | TODO | TODO |
| Writes | TODO | TODO |
| Burst allowance | TODO | TODO |
Headers
TODO: header names undecided. The intended set, following the RateLimit
convention:
| Header | Meaning |
|---|---|
RateLimit-Limit | Requests permitted in the current window |
RateLimit-Remaining | Requests left |
RateLimit-Reset | Seconds until the window resets |
Retry-After | Sent on 429 only — seconds to wait |
Read Retry-After rather than guessing. It is the only value that accounts for
the server's view of your budget.
Backing off
local HttpService = game:GetService("HttpService")
local function requestWithBackoff(options, attempts)
attempts = attempts or 4
for attempt = 1, attempts do
local response = HttpService:RequestAsync(options)
if response.StatusCode ~= 429 then
return response
end
-- Honour the server's number when it sends one; otherwise back off
-- exponentially. The jitter matters: without it every server in the
-- experience retries on the same tick and arrives as one spike.
local retryAfter = tonumber(response.Headers["retry-after"])
local delay = retryAfter or (2 ^ attempt)
task.wait(delay + math.random() * 0.5)
end
return nil
endStaying under the limit
Batch where you can. One call at the end of a round beats one call per scoring event. XP is additive, so accumulating locally and flushing once is equivalent for the member and far cheaper for you.
Do not poll for data you already have. A game server that just awarded XP already knows the result — it came back in the response. Re-reading the member afterwards doubles your request count for nothing.
Cache reads. Clan and rank structures change rarely. Fetching the rank list once per server start is almost always enough.