If you have tried to read Roblox data from inside a Roblox game, you have met this:
HTTP 403 (Forbidden)
HttpService is not allowed to access ROBLOX resources
Roblox blocks its own game servers from calling its own web API. That is deliberate, it applies to every roblox.com domain and every path, and no combination of headers or options switches it off. It is also the single reason an entire category of tooling exists.
This page covers what the restriction actually is, the three real ways around it, and the part nobody publishes: the measured per-endpoint rate limits, which decide whether your project needs one address or fifty. Every number below was measured on 11 August 2026 rather than repeated from a forum thread.
What is actually blocked
HttpService is Roblox's outbound HTTP client. It reaches the open internet fine, and it refuses Roblox itself. So a game that wants to show a player's group rank, badge count, inventory, friend count or avatar has to get that data from somewhere other than a direct call.
Alongside it, a few limits catch people out:
- Ports below 1024 are blocked, except 80 and 443. Port 1194 is blocked outright.
- HTTP requests must be enabled per experience under File, Experience Settings, Security.
- Open Cloud calls from a game server are capped at 2500 per minute per server.
The reason for the roblox.com block is straightforward once you think like the platform: a game server is code written by a third party running on Roblox's own infrastructure, already holding a trusted network position. Letting it speak directly to Roblox's internal endpoints would make every experience a potential launchpad against the platform.
The way around it: swap the domain
Because the block is on the destination domain, routing the same request through a host that is not Roblox works. That is all a Roblox API proxy is: a relay that receives your request, forwards it to Roblox, and hands the answer back.
The best-known public one is RoProxy, announced on the Developer Forum in October 2021 after its predecessor rprxy.xyz shut down. Usage is a one-word change. Keep the subdomain, keep the path, swap roblox.com for roproxy.com:
https://users.roblox.com/v1/users/1 blocked, 403
https://users.roproxy.com/v1/users/1 works
In Luau that looks like this:
local HttpService = game:GetService("HttpService")
-- users.roblox.com is refused by HttpService, users.roproxy.com is not.
-- Same path, same response shape, different host.
local function getUser(userId)
local ok, body = pcall(function()
return HttpService:GetAsync("https://users.roproxy.com/v1/users/" .. userId)
end)
if not ok then
warn("roblox api unreachable: " .. tostring(body))
return nil
end
return HttpService:JSONDecode(body)
end
local user = getUser(1)
if user then
print(user.name, user.displayName, user.created)
end
The pcall is not decoration. A proxy is a third party that can be down, and an uncaught GetAsync failure throws.
RoProxy, measured
Public write-ups of RoProxy tend to be either the 2021 announcement or someone complaining it is broken. We tested it. On 11 August 2026 every subdomain we tried returned real Roblox data:
users, games, thumbnails, groups, friends, catalog and inventory all answered 200 through roproxy.com.
Latency, twelve runs each on the same endpoint:
| Route | Median | Slowest |
|---|---|---|
users.roblox.com direct | 0.173s | 0.185s |
users.roproxy.com | 0.178s | 0.300s |
The proxy hop costs about five milliseconds at the median. The common assumption that proxying makes API calls slow does not hold here.
It also does not throttle you on its own account: forty rapid requests returned forty 200s, on an endpoint where direct access starts refusing after thirty. It passes Roblox's own x-ratelimit-remaining header through, so you can watch the upstream budget from your side.
Which RoProxy subdomains actually work
RoProxy mirrors the subdomain, so anything that exists on roblox.com should
exist on roproxy.com. Mostly it does. We swept the ones people use:
| Subdomain | Relays? |
|---|---|
users, groups, games, thumbnails, friends | Yes |
catalog, inventory, economy, avatar, badges | Yes |
apis (the newer Open Cloud host) | Yes |
accountinformation, premiumfeatures | Relays, but the endpoints need a session |
presence | No. Returns 404 |
presence.roproxy.com is not covered. If you need online status, that one
call has to go somewhere else, which is a good reason to know how to run your own.
The catch, in its operator's own words
RoProxy's operator is candid about it:
I can't recommend RoProxy (or any public proxy) for production applications. Albeit rare, downtime and slowdowns are inevitable.
The thread bears that out: endpoints reported going down every couple of weeks, an invalid TLS certificate in April 2025, 401s on inventory endpoints, and the gamepass endpoint returning nothing in November 2025. There is also no documentation. roproxy.com itself returns a redirect at the root and 429 on /docs, /api and /faq. The documentation is a forum thread, which is why so many people search for one.
So: RoProxy is excellent for a hobby project, a prototype, or anything where a bad hour is survivable. It is a dependency you do not control on the critical path of anything that matters.
The part nobody publishes: the actual rate limits
Roblox meters requests per endpoint, per IP address, and it tells you the budget in a response header on ordinary successful calls:
x-ratelimit-limit: 30, 30;w=60
x-ratelimit-remaining: 0
x-ratelimit-reset: 3
retry-after: 5
x-retry-after-coverage: /v1/users/{userId}/
Because that header comes back on a 200, the entire map costs one request per endpoint.
Read the w= part before you trust the number. It is the window in seconds, and it is not always 60. 30, 30;w=60 means thirty per minute. 1000, 1000;w=1 means a thousand per second. Two endpoints below use a one-second window, and reading only the first number understates them by 60x. We made exactly that mistake on the first pass, and it is the reason this section is worth reading rather than skimming.
Here is what the headers returned, all public and unauthenticated:
| Endpoint | Window | Requests per minute, per IP |
|---|---|---|
users.roblox.com/v1/users/{id}/username-history | 60s | 1 |
users.roblox.com/v1/users/search | 60s | 1 |
games.roblox.com/v1/games/{id}/servers/Public | 60s | 3 |
groups.roblox.com/v1/groups/{id} | 60s | 7 |
catalog.roblox.com/v1/search/items | 60s | 12 |
users.roblox.com/v1/users/{id} | 60s | 30 |
avatar.roblox.com/v1/users/{id}/avatar | 60s | 40 |
thumbnails.roblox.com/v1/users/avatar | 60s | 60 |
inventory.roblox.com/v1/users/{id}/items/GamePass/{id}/is-owned | 60s | 60 |
friends.roblox.com/v1/users/{id}/friends/count | 60s | 100 |
inventory.roblox.com/v1/users/{id}/categories | 60s | 120 |
games.roblox.com/v2/users/{id}/games | 60s | 200 |
games.roblox.com/v1/games?universeIds= | 60s | 300 |
groups.roblox.com/v2/users/{id}/groups/roles | 60s | 500 |
badges.roblox.com/v1/badges/{badgeId} | 60s | 600 |
economy.roblox.com/v2/assets/{id}/details | 60s | 1000 |
groups.roblox.com/v1/groups/{id}/users | 60s | 1200 |
thumbnails.roblox.com/v1/games/icons | 60s | 1200 |
groups.roblox.com/v2/groups?groupIds= | 1s | 60 |
users.roblox.com/v1/users (POST) | 1s | 60,000 |
A spread from 1 per minute to 60,000. There is no such thing as "the Roblox API rate limit"; there is only the limit on the endpoint you happen to be calling, and the tight ones are very tight.
Two things the table will not tell you unless you look. badges.roblox.com/v1/users/{id}/badges advertises 1000 a minute and then answers 9002 Authentication token is missing for every user we tried, so it is not usable unauthenticated whatever its budget says. And endpoints on the same subdomain differ wildly: group lookups by ID allow 7 a minute while the member list on the same host allows 1200.
Those headers match reality. We confirmed three by deliberately tripping them:
| Endpoint | Header said | 429 arrived on request |
|---|---|---|
groups/{id} | 7 | #8 |
username-history | 1 | #2 |
servers/Public | 3 | #4 |
The endpoints that break projects are the low ones, so those are worth seeing on their own:
The limit follows the IP, which turns this into arithmetic
This is the finding that decides your architecture. While our address was already refused on group lookups, the identical request through RoProxy succeeded in the same moment:
direct groups.roblox.com/v1/groups/1 -> 429
via groups.roproxy.com/v1/groups/1 -> 200
Same endpoint, same second, different exit address. The budget is attached to the address, not to your account, your key or your game.
So sustained throughput has three terms, and most people only think about one:
records per minute = endpoint limit x records per request x clean exit addresses
The middle term is the one nearly everyone leaves at 1, and it is worth far more than the other two.
Batch endpoints, which are worth more than any number of IPs
Roblox has multi-get and batch endpoints for most of the data people fetch in loops. We measured each one's ceiling by sending oversized requests until it named its own limit:
| Endpoint | Method | Max per request | Limit | Records per minute, one IP |
|---|---|---|---|---|
users.roblox.com/v1/users | POST | 200 user IDs | 1000/s | effectively unbounded |
users.roblox.com/v1/usernames/users | POST | usernames | 500/min | very high |
groups.roblox.com/v2/groups?groupIds= | GET | 100 group IDs | 1/s | 6,000 groups |
thumbnails.roblox.com/v1/batch | POST | 100 items | 60/min | 6,000 thumbnails |
thumbnails.roblox.com/v1/users/avatar?userIds= | GET | 100 user IDs | 60/min | 6,000 avatars |
presence.roblox.com/v1/presence/users | POST | 50 user IDs | 60/min | 3,000 presences |
Now compare each against the obvious single-record call:
| What you want | The loop most people write | The batch call | Difference |
|---|---|---|---|
| User profiles | GET /v1/users/{id}, 30/min | POST /v1/users, 200 per call | thousands of times faster |
| Group details | GET /v1/groups/{id}, 7/min | GET /v2/groups?groupIds=, 100 per call | 857x |
| Avatar thumbnails | one ID at a time, 60/min | 100 IDs per call | 100x |
The group case is the one worth staring at. v2/groups looks worse on paper: its header says 1, 1;w=1, a budget of one. But that is one request per second carrying 100 groups, which is 6,000 groups a minute against 7 for the single-ID endpoint. The endpoint with the most alarming rate limit is 857 times faster than the friendly-looking one.
The exceptions matter as much. Some endpoints have no batch form at all, and those are the ones that genuinely cap you: users/search at 1 a minute, username-history at 1, servers/Public at 3, catalog search at 12. If your work lives on those, no amount of rewriting helps and the only remaining lever is the third term.
So the order of operations is not negotiable:
- Cache. Most Roblox data changes slowly. This usually removes the problem outright.
- Batch. Fixing the middle term is worth more than anything you can buy, and it is free.
- Then add addresses, for the endpoints that have no batch form, or when batching is already maxed and you still need more.
Only after the first two does the arithmetic for step three make sense. Fetching 10,000 group profiles takes 100 batched requests. On one address at one per second that is about 100 seconds. Doing it with the single-ID endpoint at 7 a minute would take 24 hours. And a job pinned to users/search at 1 a minute needs one address per lookup per minute, which is where buying addresses is genuinely the only answer.
We would rather you fixed the batching and bought nothing. A page that tells you to buy fifteen addresses for a job that needs one badly-written loop replaced is not worth reading.
The batch call in Luau
POST through the proxy works the same way the GET does, and replaces up to two hundred separate lookups:
local HttpService = game:GetService("HttpService")
-- One request for up to 200 users, against 200 requests at 30 per minute.
-- Roblox returns ONLY the users it found, so the result is keyed by id rather
-- than assumed to line up with the input order.
local function getUsers(userIds)
local ok, body = pcall(function()
return HttpService:PostAsync(
"https://users.roproxy.com/v1/users",
HttpService:JSONEncode({ userIds = userIds, excludeBannedUsers = false }),
Enum.HttpContentType.ApplicationJson
)
end)
if not ok then
warn("batch user lookup failed: " .. tostring(body))
return {}
end
local byId = {}
for _, user in ipairs(HttpService:JSONDecode(body).data) do
byId[user.id] = user
end
return byId
end
local users = getUsers({ 1, 156, 261 })
for id, user in pairs(users) do
print(id, user.name, user.displayName, user.hasVerifiedBadge)
end
Do not assume the response is the same length as the request. Asking for user IDs 1 to 100 returned 88 records, because deleted and non-existent IDs are simply absent rather than returned as nulls. Code that zips the response against the request array by index will silently attach the wrong name to the wrong player. Key by id, as above.
The same shape works for the other batch endpoints, with their own ceilings: 100 for thumbnails and groups, 50 for presence. Exceed them and Roblox tells you plainly, with Too many ids., There are too many requested Ids. or Too many User Ids being sent in the request. respectively, as a 400 rather than a 429. Those two failures mean opposite things: a 400 says your request was too big, a 429 says you sent too many requests. Retrying a 400 forever is a bug we have watched people ship.
Endpoint reference
Everything below is public and answers without authentication. Measured 11 August 2026. Limits are per IP; where the window is one second the per-minute figure is given, because that is the number you actually plan against.
users.roblox.com
| Endpoint | Returns | Limit |
|---|---|---|
GET /v1/users/{id} | Profile: name, displayName, description, created, isBanned | 30/min |
POST /v1/users | Up to 200 profiles in one call | 1000/s |
POST /v1/usernames/users | Usernames to IDs and profiles | 500/min |
GET /v1/users/{id}/username-history | Previous usernames | 1/min |
GET /v1/users/search?keyword= | User search | 1/min |
thumbnails.roblox.com
| Endpoint | Returns | Limit |
|---|---|---|
GET /v1/users/avatar?userIds= | Full-body renders, up to 100 IDs | 60/min |
GET /v1/users/avatar-headshot?userIds= | Headshots, up to 100 IDs | 120/min |
GET /v1/games/icons?universeIds= | Game icons | 1200/min |
GET /v1/assets?assetIds= | Asset images | 3,000/min (w=1) |
POST /v1/batch | Mixed types, up to 100 items | 60/min |
Check state before you use imageUrl. A thumbnail row is not always
usable: asking for a target that does not exist returned state: "Blocked" with
errorCode: 0 and no image, delivered as an ordinary row inside a 200 response
rather than as an error. Code that reads data[i].imageUrl without looking at
state renders a broken image and never logs anything.
groups.roblox.com
| Endpoint | Returns | Limit |
|---|---|---|
GET /v1/groups/{id} | Group details, owner, member count | 7/min |
GET /v2/groups?groupIds= | Same, up to 100 groups per call | 60/min (w=1) |
GET /v1/groups/{id}/roles | Rank list with member counts | 800/min |
GET /v1/groups/{id}/users | Member list, paginated | 1200/min |
GET /v2/users/{id}/groups/roles | Every group a user is in, with rank | 500/min |
games.roblox.com
| Endpoint | Returns | Limit |
|---|---|---|
GET /v1/games?universeIds= | Name, description, playing, visits, favourites | 300/min |
GET /v1/games/votes?universeIds= | Likes and dislikes | 200/min |
GET /v2/users/{id}/games | Games created by a user | 200/min |
GET /v1/games/{id}/servers/Public | Live server list | 3/min |
Everything else
| Endpoint | Returns | Limit |
|---|---|---|
POST presence.roblox.com/v1/presence/users | Online status, up to 50 IDs | 60/min |
GET inventory.roblox.com/v1/users/{id}/items/GamePass/{passId}/is-owned | true or false | 60/min |
GET inventory.roblox.com/v1/users/{id}/categories | Inventory categories | 120/min |
GET friends.roblox.com/v1/users/{id}/friends/count | Friend count | 100/min |
GET friends.roblox.com/v1/users/{id}/followers/count | Follower count | 100/min |
GET avatar.roblox.com/v1/users/{id}/avatar | Worn items and body colours | 40/min |
GET economy.roblox.com/v2/assets/{id}/details | Asset price, creator, sales | 1000/min |
GET badges.roblox.com/v1/badges/{badgeId} | Badge details | 600/min |
GET catalog.roblox.com/v1/search/items | Catalog search | 12/min |
GET apis.roblox.com/game-passes/v1/game-passes/{id}/product-info | Gamepass name, price, creator | 6,000/min (w=1) |
Two that look public and are not: badges.roblox.com/v1/users/{id}/badges returns
9002 Authentication token is missing for every user, and
catalog.roblox.com/v1/catalog/items/details (POST) returns 403 XSRF token invalid. Both advertise generous limits they will not let you spend.
api.roblox.com no longer exists. It does not resolve in DNS at all, while
users.roblox.com answers normally from the same machine. This matters because
it is the host in sentanos/ProxyService's own README example, so the most-forked
Roblox proxy module on GitHub ships a sample pointing at a dead domain and a
Heroku free tier that ended in 2022. Anything built by copying it starts broken.
Recipes for the things people actually ask
Get a player's rank in your group. One call, and it returns every group at once, so filter client-side rather than asking per group:
local roles = HttpService:JSONDecode(HttpService:GetAsync(
"https://groups.roproxy.com/v2/users/" .. userId .. "/groups/roles")).data
for _, entry in ipairs(roles) do
if entry.group.id == MY_GROUP_ID then
print(entry.role.name, entry.role.rank) -- rank is 0-255
end
end
Check gamepass ownership. The endpoint returns a bare true or false, not
an object, which trips people up:
local owned = HttpService:GetAsync(
"https://inventory.roproxy.com/v1/users/" .. userId ..
"/items/GamePass/" .. passId .. "/is-owned") == "true"
Turn usernames into user IDs. Never scrape a profile page for this. POST /v1/usernames/users does it in bulk and returns requestedUsername alongside
each result, so you can match them up even when Roblox corrects capitalisation.
Show a player's avatar. Use avatar-headshot rather than avatar: it is
double the rate limit (120 against 60), takes 100 IDs per call, and is the image
people expect next to a name.
Check who is online, with a caveat. POST presence.roblox.com/v1/presence/users
takes up to 50 IDs and userPresenceType is 0 offline, 1 website, 2 in an
experience, 3 in Studio. But every user we queried unauthenticated came back
0 with lastLocation: "Website", including accounts that were not plausibly
all offline. The endpoint answers 200 without a session and appears to return a
stub, so treat unauthenticated presence as unreliable and verify it against a
known-online account before building on it.
Get live player counts. GET /v1/games?universeIds= gives playing and
visits for many universes at once at 300/min. Do not use the public server list
for this: it is 3/min and paginated, and it is the wrong tool for a number the
games endpoint hands you directly.
Running your own proxy
Since the block is only on the destination domain, a proxy is a small piece of software. Several ready-made options exist:
| Option | What it is | Reality |
|---|---|---|
| RoProxy | Free public relay | Fast, unthrottled, no docs, no uptime promise |
| Cloudflare Worker | Deploy a small script, 100k requests/day free | Works, but see the trap below |
askfalse/roproxy-lite | RoProxy's own code for self-hosting, in Go | Self-managed, you supply the host |
sentanos/ProxyService | The classic Luau module, 1,661 forks | Last code push August 2024, and its example points at a Heroku URL although Heroku ended its free tier in November 2022 |
The Cloudflare Worker trap
Deploying a Worker as a Roblox proxy is the most-recommended free route, and developers who follow it keep reporting the same thing: 429 errors after roughly four requests.
Nothing is wrong with the code. A Worker exits through Cloudflare's shared address ranges, which an enormous number of other people are also using against Roblox. You are not getting a budget, you are getting whatever is left of someone else's. It is the same reason a free proxy list fails at this, described in free proxies for Roblox.
This is worth understanding rather than working around, because it is the moment the real variable becomes visible. Proxy software is not the scarce resource. The exit address is. Any solution where you share an address with strangers inherits their consumption, whether that address belongs to Cloudflare, a free proxy list, or a public relay everyone else has also discovered.
The whole proxy, in twenty lines
People reach for a template repo for this, which is why so many abandoned ones exist. The entire job is rewriting a hostname:
// /users/v1/users/1 -> https://users.roblox.com/v1/users/1
export default {
async fetch(request) {
const url = new URL(request.url);
const [, subdomain, ...rest] = url.pathname.split("/");
if (!subdomain) return new Response("usage: /<subdomain>/<path>", { status: 400 });
// Anyone who finds this URL spends YOUR rate-limit budget. Gate it.
if (request.headers.get("x-proxy-key") !== SHARED_SECRET) {
return new Response("forbidden", { status: 403 });
}
const target = new URL(`https://${subdomain}.roblox.com/${rest.join("/")}`);
target.search = url.search;
const upstream = await fetch(target, {
method: request.method,
headers: { "Content-Type": request.headers.get("Content-Type") ?? "application/json" },
body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
});
// Pass Roblox's own budget headers back, so callers can see what is left.
return new Response(upstream.body, { status: upstream.status, headers: upstream.headers });
},
};
Two details that are not optional. Gate it with a shared secret, because an open proxy on a public workers.dev URL is an invitation to spend your budget for you, which is how several of the abandoned public proxies died. And forward the upstream headers unchanged, so x-ratelimit-remaining and retry-after survive the hop; a proxy that swallows them leaves your client flying blind into 429s.
Point it at a host whose exit address you control and it behaves like a private RoProxy. Leave it on Cloudflare's shared ranges and it will 429 early, for the reason above.
Caching, which is the cheapest fix available
Roblox data changes far more slowly than people poll it, so cache by how volatile the field actually is rather than picking one TTL for everything:
| Data | Volatility | Sensible cache |
|---|---|---|
| Usernames, display names, account creation date | Almost never | Hours to a day |
| Group name, owner, description | Rarely | Hours |
| Thumbnail URLs | Rarely (and the image is CDN-hosted anyway) | Hours |
| Group member counts, badge counts | Slowly | Minutes |
| Live player counts, asset prices | Constantly | Seconds to a minute |
| Presence | Constantly | Do not cache, and see the caveat above |
A five-minute cache on user and group lookups removes the rate-limit problem for most projects outright, which is why it comes before batching and long before buying anything.
The sanctioned path: Open Cloud
Not everything needs a proxy. Roblox's Open Cloud API is the supported, authenticated route into platform data, using API keys or OAuth 2.0 rather than the legacy cookie-authenticated web endpoints. Roblox is explicit that Open Cloud endpoints carry real stability guarantees, while the legacy web API can change without notice.
Use Open Cloud when it covers what you need. It is documented, it will not disappear under you, and it does not depend on a relay someone else maintains for free. Reach for a proxy when the data you want only exists on the legacy web endpoints, which is still a large surface, including much of the group, inventory, catalog and social data people build around.
Worth knowing: legacy web endpoints are deprecated in batches. A set of older game pass and developer product endpoints was retired on 23 April 2026 in favour of Open Cloud equivalents. If you are starting a project now, check Open Cloud first and treat the legacy surface as the fallback rather than the default.
Choosing between them
Hobby project, a game feature, a Discord bot for your friends. Use RoProxy. It is free, it is fast, and if it has a bad afternoon nothing important breaks. Cache aggressively and wrap every call in pcall.
Something people rely on. Do not put a free public relay on your critical path, on the operator's own advice. Self-host the proxy so you control the code, and give it an exit address you alone use so you get a full rate-limit budget instead of a shared one. That is usually a dedicated IPv4 address, from $0.30/IP, and one address per unit of throughput you need.
Account-touching work, anything that logs in or manages accounts rather than reading public data, is a different problem with different requirements. That belongs on residential addresses, from $0.44/GB, and is covered in proxies for Roblox.
Whichever you pick, confirm the address actually carries traffic before you build on it. Our proxy checker opens a real connection and reports the exit IP, country, latency and anonymity in one pass, and the manual method is in how to check if a proxy is working.
Error reference
Every one of these came out of the testing above, so the wording is what you will actually see rather than a paraphrase.
| Status | Body | What it means |
|---|---|---|
| 403 | HttpService is not allowed to access ROBLOX resources | You called a roblox.com domain from a game server. Use a proxy host. |
| 429 | Too many requests. (code 4) | Rate limited. Read retry-after and wait. |
| 400 | Too many ids. (code 1) | POST /v1/users batch over 200. Split it. |
| 400 | There are too many requested Ids. (code 1) | Thumbnails batch over 100. |
| 400 | Too many ids in request. (code 2) | v2/groups over 100 group IDs. |
| 400 | Too many User Ids being sent in the request. (code 3) | Presence over 50 IDs. |
| 401 | Authentication token is missing (code 9002) | Endpoint needs a logged-in session. A proxy does not fix this. |
| 403 | XSRF token invalid | Endpoint needs an XSRF token, obtained by a prior authenticated request. |
The distinction that costs people the most: 429 is temporary, 400 is permanent. A 400 about ID counts will fail identically on every retry until you change the request size. Backing off and retrying it is an infinite loop.
A short production checklist
- Cache first. Most Roblox data changes slowly. A five-minute cache on group and user lookups usually removes the rate-limit problem entirely.
- Batch second. If you are calling
/v1/users/{id}in a loop, you are leaving a 200x multiplier on the table for free. Check for a multi-get form before you touch anything else. - Read the headers, including
w=.x-ratelimit-remainingtells you how much budget is left before you spend it, and the window tells you what the limit even means. Back off at low values instead of waiting for the 429. - Tell 400 and 429 apart. A 400 saying
Too many ids.means your batch was too big and will fail identically forever. A 429 means slow down. Retrying the first as if it were the second is a loop that never exits. - Honour
retry-after. A 429 tells you exactly how long to wait. Retrying immediately just spends the next window. - Wrap every call. A proxy is a third party.
pcalleverything and decide what your game shows when the data does not arrive. - Measure the endpoint you actually use. The limits above were measured on one date from one address. Send one request and read the header rather than trusting a table, including this one.
- Do not authenticate through a proxy you do not control. Public relays are for public data. A
.ROBLOSECURITYcookie sent through a stranger's server is an account handed over, as explained in free proxies for Roblox.
The bottom line
Roblox blocks HttpService from reaching roblox.com, so reading platform data from a game server needs a relay on a non-Roblox domain. RoProxy does that well and free, and its own operator tells you not to depend on it.
The number that governs your project is not which proxy you pick. It is the per-endpoint, per-IP limit, which runs from 1 request a minute to 60,000, multiplied by how many records you fit in each request. Cache first, batch second, and most projects never need a second address: swapping a loop over /v1/users/{id} for one POST /v1/users is worth more than anything you could buy.
Buy addresses for the cases batching cannot reach, which are real: user search at 1 a minute, username history at 1, public server lists at 3, catalog search at 12. There, throughput is simply the limit multiplied by the number of exit addresses you do not share with anyone, and that is why a Cloudflare Worker dies after four requests while a dedicated address does not. Same code, different address, different budget.
Sources
- Roblox Developer Forum, RoProxy.com, a free rotating proxy for Roblox APIs, including the operator's production advice. 364 replies, 160,501 views.
- Roblox Developer Forum, How to make a free self-hosted Roblox proxy using Cloudflare, including the 429 reports.
- Roblox Creator Hub, In-game HTTP requests, for port restrictions and the 2500-per-minute Open Cloud limit.
- Roblox Creator Hub, Cloud API reference, for Open Cloud stability guarantees against legacy endpoints.
- GitHub, sentanos/ProxyService and askfalse/roproxy-lite.
- Rate limits, batch ceilings, latency and throughput measured by HProxy on 11 August 2026, unauthenticated, from a single residential address. Limits were read from
x-ratelimit-limitresponse headers and three were confirmed by deliberately tripping them. Batch ceilings were found by sending oversized requests until each endpoint named its own maximum, with cooldowns between attempts so a 429 could not be mistaken for a size rejection.