Ranking API
Change a Roblox group rank from anywhere (a game server, a Discord bot, a scheduled job) with one authenticated request. The same guardrails and audit trail apply as when you click the button in the dashboard.
How it works
Your service sends a request to Rostack. Rostack authenticates the API key, resolves it to a workspace, applies the guardrails, then calls Roblox's official Open Cloud API using the group's own key. Your service never handles a Roblox credential.
request path
your game / bot
│ POST /api/v1/rank (Authorization: Bearer rsk_live_…)
▼
Rostack ── authenticate key ── check guardrails ── write audit entry
│
│ assignRole (x-api-key: your group's Open Cloud key)
▼
Roblox Open Cloud ── rank changedBecause both the dashboard and the API funnel through the same routine, a rank change made by a bot is indistinguishable in the audit log from one made by a person, except that the actor is recorded as the key that did it.
Setup
- 1
Connect a Roblox Open Cloud key
In your workspace, open
Rankingand connect an Open Cloud API key created at Creator Dashboard → API Keys. It needs thegroup-membershipAPI with your group added, and bothgroup:readandgroup:write. Rostack encrypts it with Cloud KMS and stores only the ciphertext. - 2
Create a Rostack API key
On the same page, create a key and give it a max rank. The key can never assign a rank at or above that number, no matter what it asks for.
Copy it immediately
The key is shown once and stored only as a hash. If you lose it, revoke it and create another. Nobody, including us, can recover the original. - 3
Store the key as a secret
Put it in an environment variable or Roblox secret store, never in a committed script. Anyone holding the key can rank up to its ceiling.
- 4
Send your first request
Call the endpoint below. A
200means Roblox has already applied the change.
The endpoint
POST https://rostack.app/api/v1/rank
Authorization: Bearer rsk_live_…
Content-Type: application/jsonRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
robloxUserId | number | Yes | The Roblox user to rank. |
rank | number | Yes | Target rank, 0–255. Must match a role that exists in your group. |
reason | string | No | Recorded in the audit log. Write something a human will understand later. |
A successful response:
200 OK
{
"ok": true,
"robloxUserId": 3961562630,
"rank": 50,
"roleName": "Moderator"
}Examples
Luau (Roblox game server)
-- Roblox game server. HttpService must be enabled.
local HttpService = game:GetService("HttpService")
local ROSTACK_KEY = "rsk_live_xxx" -- store this in a secret, not in a script
local function setRank(userId: number, rank: number, reason: string)
local ok, res = pcall(function()
return HttpService:RequestAsync({
Url = "https://rostack.app/api/v1/rank",
Method = "POST",
Headers = {
["Authorization"] = "Bearer " .. ROSTACK_KEY,
["Content-Type"] = "application/json",
},
Body = HttpService:JSONEncode({
robloxUserId = userId,
rank = rank,
reason = reason,
}),
})
end)
if not ok then
warn("rostack request failed:", res)
return false
end
if not res.Success then
warn("rostack rejected:", res.StatusCode, res.Body)
return false
end
return true
end
setRank(3961562630, 50, "Reached 100 activity points")TypeScript (Discord bot or worker)
// Discord bot, worker, or any Node service.
const res = await fetch("https://rostack.app/api/v1/rank", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROSTACK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
robloxUserId: 3961562630,
rank: 50,
reason: "Passed the moderator interview",
}),
});
const body = await res.json();
if (!res.ok) {
// body.error is a stable machine code; body.message is for humans.
throw new Error(`${body.error}: ${body.message}`);
}
console.log(`Now ${body.roleName} (rank ${body.rank})`);curl
curl -X POST https://rostack.app/api/v1/rank \
-H "Authorization: Bearer rsk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"robloxUserId": 3961562630,
"rank": 50,
"reason": "Reached 100 activity points"
}'Errors
Every failure returns a stable error code and a human message. Branch on the code, show the message.
| Status | Code | What it means |
|---|---|---|
| 401 | unauthorized | The key is unknown or revoked. Deliberately the same message for both. Otherwise the endpoint could be used to probe which keys once existed. |
| 403 | rank_above_key_ceiling | The requested rank is at or above the key's max rank. |
| 403 | roblox_unauthorized | The workspace has no Open Cloud key connected, or Roblox rejected it. |
| 400 | rank_rule_invalid | No role exists at that rank, or the rank is the owner (255). |
| 400 | invalid_request | The body did not validate. The response lists the expected shape. |
Guardrails
These are enforced server-side on every call, including from the dashboard. They cannot be turned off, because the failure they prevent is not recoverable.
| Rule | Why |
|---|---|
| Rank 255 can never be assigned | 255 is the group owner. Transferring ownership is not something a bot should be able to do by sending a number. |
| A key cannot assign at or above its own ceiling | A leaked game-server key should be able to promote a moderator, not create an administrator. |
| The target role must exist | Roblox would otherwise silently accept a rank with no matching role. |
| Every attempt is written to the audit log before it runs | A crash mid-call still leaves evidence that it was attempted, with the actor and reason. |
Rate limits
Rotating a key
