Introduction
Ever wanted to rank people in your group without NodeJS, Noblox, or a third party service?
Well now you can, with the introduction of [Beta] Use Open Cloud via HttpService Without Proxies!
It’s a bit complicated to setup, but it’s worth it in the future!
Small note:
- API keys will need to be encrypted in Base 64 for Roblox Studio only, which can be done in a third party website.
- It’s currently in beta, and the code I provided is quick sample code I made. Not the best, but it works!
Setup:
1st Step: Creating the OpenCloud API Key!
Step 1: Creating the API Key!
You’ll have to first create a OpenCloud API Key on Roblox!
You can do this by heading to the OpenCloud API Keys dashboard!
Make sure you’re on a PROFILE and not a group, group API keys are currently bugged!
Don’t worry as well, API Keys made on a profile will work in group games!
Step 2: Naming your API key.
You’ll want to give your key a name, this can be anything.
It doesn’t really matter as we’ll set it as a different secret name later in the game you wish to rank people in.
Step 3: API Access Permissions
We’ll only need group:read
and group:write
!
Step 4: Accepted IP addresses.
You’ll have to set the IP as 0.0.0.0/0
for right now.
There might be a different option in the future as stated in this post.
Step 5: Copying your API key!
This is needed, as we’ll be encrypting the key in Base 64!
2nd Step: Encrypting your API Key to Base64 for Roblox Studio!
I personally like using CyberChef for encryption!
Any other Base64 encryption website will work though!
Make sure you have it set to To Base64
! This is needed for Roblox Studio Secrets as stated here.
3rd step: Creating the secret!
Note:
We’ll have to set the secret in two places!
First in the main experience, and the next in studio!
1st place: Setting the secret on the website!
Head to the secret tab in your experience page!
Make sure the key you’re adding in is the original key! [Not base 64 enocded.]
And that’s all you need to do for the website!
2nd place: Setting the secret in Roblox Studio!
Head to GameSettings → Security tab!
This is where we’ll set our Secret in studio!
This key will need to be in Base64!
We’ll use a template like in this documentation to make the API key to work in studio!
{"secretName": ["YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", "*.roblox.com"]}
Should look something like this after we add it in!
Make sure the secretName is changed to the same secret name on the website!
4th step: Adding/editing the sample code!
I made a ModuleScript for Group ranking!
If you set up the secret correctly, and Group ID in the script, it should work!
Make sure the API key name is right in the code as well!
It could be different if you set it differently in step 3! :3
This code was made quickly, so it’s not the best quality.
Marketplace sample moduleScript!
Modulescript Code in text:
--!strict
--[[
Name: Group OpenCloud Module
Author: va0ck
Description: Wrapper for OpenCloud group!
Date created: 2025/06/02
Instructions:
1. Enable HttpService!
2. Create a OpenClould API key from a USER ACCOUNT. [Group keys are broke at the moment!]
Make sure to allow group:write!
Also make sure to allow 0.0.0.0/0!
3. Encode the OpenCloud API key in Base64! [Cyberchef is helpful for Base64!]
4. Create a secret in both Studio & Roblox Website with the API key! [Studio for local development, Website for live games!]
5. You should be good to edit the rank settings! :D
]]--
----------------
--//Settings//--
----------------
local GROUP_ID = 0
local SECRET_KEY_NAME = "roblox_cloud_key"
local MAX_RETRY_ATTEMPTS = 3
----------------
--//Services//--
----------------
local HttpService = game:GetService("HttpService")
------------------
--//Main Module//-
-------------------
local GroupCloudModule = {}
GroupCloudModule.API_KEY = HttpService:GetSecret(SECRET_KEY_NAME)
type RankIdTableType = {rankId: number, rank: number}
type RankIdsListType = {RankIdTableType}
GroupCloudModule.CACHED_RankIds = {} :: RankIdsListType
--------------------------------
--//Base OpenCloud Functions//--
--------------------------------
local function getRobloxAPI(url: string)
local success, response = pcall(function()
return HttpService:RequestAsync({
Url = url,
Method = "GET",
Headers = {
["x-api-key"] = GroupCloudModule.API_KEY,
["Content-Type"] = "application/json",
},
})
end)
return success, response
end
local function postOrPatchRobloxAPI(url:string, method: "POST"|"PATCH", data: {string})
local success, response = pcall(function()
return HttpService:RequestAsync({
Url = url,
Method = method,
Headers = {
["x-api-key"] = GroupCloudModule.API_KEY,
["Content-Type"] = "application/json",
},
Body = HttpService:JSONEncode(data),
})
end)
return success, response
end
local function requestRobloxAPI(url: string, method: string, data: {string}?)
local attemptIndex = 0
local success, response
repeat
if method == "GET" then
success, response = getRobloxAPI(url)
elseif (method == "PATCH" or method == "POST") then
assert(data, "Data is required for Patch/Post to Roblox!")
success, response = postOrPatchRobloxAPI(url, method, data)
end
if (not success) then task.wait(1) end
attemptIndex += 1
until success or attemptIndex >= MAX_RETRY_ATTEMPTS
return success, response
end
--------------------------------------
--//Ranking API Wrapper Functions//--
-------------------------------------
local function getMembershipId(userId: number): (boolean, string)
local membershipFilter = `user == 'users/{userId}'`
local searchSuccess, responseBody = requestRobloxAPI(
`https://apis.roblox.com/cloud/v2/groups/{GROUP_ID}/memberships?maxPageSize=10&filter={membershipFilter}`,
"GET"
)
responseBody = HttpService:JSONDecode(responseBody.Body)
if searchSuccess and responseBody.groupMemberships and
responseBody.groupMemberships[1] and
responseBody.groupMemberships[1].path
then
--Path format: groups/{group_id}/memberships/{group_membership_id}
local unfilteredText = responseBody.groupMemberships[1].path
unfilteredText = string.split(unfilteredText, "/")
return true, unfilteredText[4]
else
return false, responseBody
end
end
local function getGroupRankIds(clearCache: boolean?): (boolean, string | RankIdsListType)
if #GroupCloudModule.CACHED_RankIds > 0 and (not clearCache) then
return true, GroupCloudModule.CACHED_RankIds
end
--Clear cache of rankIds.
table.clear(GroupCloudModule.CACHED_RankIds)
local UNCLEANED_ROLES = {}
local rolesSuccess, rolesBody = requestRobloxAPI(
`https://apis.roblox.com/cloud/v2/groups/{GROUP_ID}/roles?maxPageSize=20`,
"GET"
)
rolesBody = HttpService:JSONDecode(rolesBody['Body'])
if rolesSuccess and rolesBody.groupRoles then
for _,v in pairs(rolesBody.groupRoles) do
table.insert(UNCLEANED_ROLES, v)
end
while rolesBody.nextPageToken ~= "" do
rolesSuccess, rolesBody = requestRobloxAPI(
`https://apis.roblox.com/cloud/v2/groups/{GROUP_ID}/roles?maxPageSize=20&pageToken={rolesBody.nextPageToken}`,
"GET"
)
if rolesSuccess and rolesBody.groupRoles then
for _,v in pairs(rolesBody.groupRoles) do
table.insert(UNCLEANED_ROLES, v)
end
else
return false, "Could not get groupRoles nextPageToken reponse body!"
end
end
for _,v in pairs(UNCLEANED_ROLES) do
table.insert(GroupCloudModule.CACHED_RankIds, {
rankId = v.id,
rank = v.rank
})
end
table.sort(GroupCloudModule.CACHED_RankIds, function(a, b)
return a.rank < b.rank
end)
return true, GroupCloudModule.CACHED_RankIds
end
return false, "Could not get first groupRoles reponse body!"
end
local function getRankNumRoleId(groupRoles: any | RankIdsListType, rankNum: number): (boolean, number | string)
for _,v in pairs(groupRoles) do
if v.rank == rankNum then
return true, v.rankId
end
end
--Clear GroupRankIdsCache with new set!
local _, _ = getGroupRankIds(true)
return false, "RankNum did not exist in group roles! (Retrieved new roleIds, try again!)"
end
local function updatePlayerMembership(membershipId: string, roleId: number | string)
local updateSuccess, updateResponse = requestRobloxAPI(
`https://apis.roblox.com/cloud/v2/groups/{GROUP_ID}/memberships/{membershipId}`,
"PATCH",
{ role = `groups/{GROUP_ID}/roles/{roleId}` }
)
return updateSuccess, `Could not update player membership: {membershipId}`
end
----------------------
--//Main Functions//--
----------------------
GroupCloudModule.UpdateRank = function(userId: number, rankNum: number): (boolean, string)
local membershipIdSucces, membershipId = getMembershipId(userId)
if (not membershipIdSucces) then return membershipIdSucces, membershipId end
local groupRolesSuccess, groupRoles = getGroupRankIds()
if (not groupRolesSuccess) then return groupRolesSuccess, groupRoles:: string end
local rankRoleIdSuccess, rankRoleId = getRankNumRoleId(groupRoles, rankNum)
if (not rankRoleIdSuccess) then return rankRoleIdSuccess, rankRoleId:: string end
local updateSuccess, updateResponse = updatePlayerMembership(membershipId, rankRoleId)
if (not updateSuccess) then return updateSuccess, updateResponse end
return true, `Updated role for {userId}!`
end
return GroupCloudModule
Final step: Testing!
Here’s small test code if you’re using my sample Modulescript!
--!strict
local GroupCloud = require(script.GroupCloud)
local rankSuccess, rankMsg = GroupCloud.UpdateRank(userId, rankId)
if (not rankSuccess) then
warn(rankMsg)
else
print(rankMsg)
end
And tada! You’ll be able to rank the user from within Roblox!
Code explanation (Optional read):
Services, types, and table used:
GROUP_ID: The group you want to rank people in.
SECRET_KEY_NAME: The name you have for both Studio and the website.
MAX_RETRY_ATTEMPT: If HttpRequest fails, it’ll wait a second and try again.
type RankIdTableType: {
rankId: The unique roleId on the website.
rank: The role number in the group.
}
GroupCloudModule.CACHED_RankIds: Roles cached so HttpRequest isn’t spammed.
Main request function!
These are made to reduce the code count,
we also include the secret into the HttpRequest headers.
Grabbing the membershipId!
First part of wanting to rank someone will be needing to get the membershipId!
This is done by using List Group Memberships and filtering it to one user.
Grabbing the group roles!
Since the roles on the website is different from the rank number,
we need to get the unique identifier of the roles!
This is done by grabbing the roles all at once and caching it with List Group Roles!
Checking the rank number!
We’ll check the Cached group roles to see if it matches
with the rank number we want to update the user with!
If it doesn’t exist, we’ll clear the list for the roles to be all cached again.
Updating the player rank!
To do this, we would need the user membershipId and website roleId!
This was all retrieved earlier in the process!
Helpful resources!
Modulescript Code | Marketplace!
HttpService Announcement | Devforum!
HttpService | Documentation!
Group API | Documentation!
Secrets stores | Documentation!