Documentation
Using OpenCloud Studio Suite
A complete walkthrough — install, connect, and use each module.
Install
- Download the installer from ocstudio.xyz and run it.
- Windows SmartScreen will warn you — the build isn't code-signed yet. Click More info → Run anyway. If you'd rather check first, upload the installer to VirusTotal; it's clean.
- Launch OpenCloud Studio Suite. All four modules open in Free mode — you can look around before connecting anything.
Connect your Roblox account
1. Create an Open Cloud API key
Open the Creator Dashboard → Credentials → API Keys and Create API Key. Add these API systems (stack them all on one key):
| API system | Operations | Needed by |
|---|---|---|
universe-places |
Write | Deploy (publishing) |
universe-datastores |
Read, List, Create, Update, Delete | Guardian bans, Insights telemetry |
groups |
group:read, group:write |
Groups (browse + join requests) |
universe-messaging-service |
Publish | Deploy (message live servers) |
Restrict it to your experience and group, set an IP allowlist (0.0.0.0/0 while
testing), pick an expiry, and copy the key — you only see it once.
2. Add it to the app
Settings → Open Cloud API keys → Add key. Paste it, give it a name, set it active, and click Test. If a module later shows a permission error, the Open Cloud response names the exact scope to add.
3. Set your targets
Settings → Target universe:
- Universe ID — Creator Dashboard → your experience → the number in the
/experiences/{universeId}/part of the URL. - Start Place ID — the game's page URL (
roblox.com/games/{placeId}), or right-click the place in Studio → Copy ID to Clipboard. - Group IDs — from a group's URL (
roblox.com/groups/{groupId}), comma-separated. - DataStore names — leave
OCSS_Bans/OCSS_Telemetryunless you changed them in the game scripts.
Click Reload. The Dashboard health checks should go green for each module whose target is set.
The modules
Deploy
Publish a .rbxl / .rbxlx as a new place version, or Save without publishing.
Every publish is written to a local history file, so rollback is just
re-publishing an earlier file. Deploy Pro also sends a MessagingService
message to your live servers (for a live-ops toggle, an announcement, etc.).
Insights
Add the InsightsTelemetryReporter script (full source in
Game scripts below) to your experience in
ServerScriptService. It writes numbers to the OCSS_Telemetry
DataStore. In the app, Snapshot now pulls the current values into a local
time series; Pro keeps full history, shows the delta vs. the last snapshot,
exports CSV, and can snapshot on a timer.
Custom metrics: _G.OCSSInsights.bump("robux_spent", price) anywhere on the server.
Groups
Pick a configured group. See its roles and (up to 250) members. Join requests lists everyone waiting to join; Accept / Decline act immediately (Pro). Roblox has no Open Cloud endpoint for changing a member's rank, so roles are read-only.
Guardian
Add the GuardianBanEnforcer script (full source in
Game scripts below) to your experience in ServerScriptService.
It reads the OCSS_Bans DataStore, kicks banned players on join, and re-checks
every 60 seconds. In the app, Add a ban (user ID + reason) writes the record;
Unban selected removes it. Every action is written to a local, append-only
audit log (Pro can export it).
Verify it works: ban your own user ID, rejoin the game, get kicked; unban, rejoin, you're back.
Game scripts
Insights and Guardian need a small server Script in your experience. Copy each
one into ServerScriptService (in Studio: right-click ServerScriptService →
Insert Object → Script, paste, rename it). They use only standard
DataStoreService — no HTTP, no external calls. Keep the store names identical to
what you set in Settings.
Studio setting: the game must have Game Settings → Security → Enable Studio Access to API Services on, and the experience must be published, for DataStore reads/writes to work.
InsightsTelemetryReporter
--!strict
-- OpenCloud Studio Suite — Insights telemetry reporter
--
-- Put this in ServerScriptService. It writes plain numbers into the DataStore
-- that Insights snapshots via Open Cloud (default name "OCSS_Telemetry", scope
-- "global"). Each key is a metric name; Insights reads every numeric entry.
--
-- Additive counters use UpdateAsync so multiple servers don't clobber each other.
-- Gauge metrics (e.g. concurrent players) are written directly — last-writer-wins,
-- which is fine for a snapshot. If you run many servers and need an accurate
-- concurrent-players number, aggregate with MemoryStore and have one job write it.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local TELEMETRY_STORE_NAME = "OCSS_Telemetry"
local FLUSH_SECONDS = 300 -- every 5 min
local store = DataStoreService:GetDataStore(TELEMETRY_STORE_NAME)
local pendingCounters: { [string]: number } = {}
--- Add to a running total (e.g. purchases, quest completions).
local function bump(metric: string, by: number?)
pendingCounters[metric] = (pendingCounters[metric] or 0) + (by or 1)
end
_G.OCSSInsights = { bump = bump }
--- Point-in-time values (written directly, last-writer-wins). Add your own.
local function collectGauges(): { [string]: number }
return {
concurrent_players = #Players:GetPlayers(),
}
end
local function setNumber(key: string, value: number)
local ok, err = pcall(function()
store:SetAsync(key, value)
end)
if not ok then
warn(("[Insights] SetAsync(%s) failed: %s"):format(key, tostring(err)))
end
end
local function addNumber(key: string, delta: number)
if delta == 0 then
return
end
local ok, err = pcall(function()
store:UpdateAsync(key, function(old)
return (tonumber(old) or 0) + delta
end)
end)
if not ok then
warn(("[Insights] UpdateAsync(%s) failed: %s"):format(key, tostring(err)))
end
end
local function flush()
for metric, value in collectGauges() do
setNumber(metric, value)
end
local toWrite = pendingCounters
pendingCounters = {}
for metric, delta in toWrite do
addNumber(metric, delta)
end
end
task.spawn(function()
while true do
task.wait(FLUSH_SECONDS)
flush()
end
end)
game:BindToClose(flush)
GuardianBanEnforcer
--!strict
-- OpenCloud Studio Suite — Guardian ban enforcer
--
-- Put this in ServerScriptService. It reads the same DataStore that Guardian's
-- ban list writes to via Open Cloud (default name "OCSS_Bans", scope "global"),
-- kicks banned players on join, and periodically re-checks players already
-- in-game so a ban placed mid-session takes effect within ~60s.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
-- keep BAN_STORE_NAME identical to Settings → "Guardian ban-list DataStore"
local BAN_STORE_NAME = "OCSS_Bans"
local RECHECK_SECONDS = 60
local FAIL_OPEN = true -- if the DataStore read errors, let the player in (true) or kick (false)
local banStore = DataStoreService:GetDataStore(BAN_STORE_NAME)
export type BanRecord = {
bannedUtc: string?,
reason: string?,
moderator: string?,
}
local function readBan(userId: number): (boolean, BanRecord?)
local ok, result = pcall(function()
return banStore:GetAsync(tostring(userId))
end)
if not ok then
warn(("[Guardian] ban lookup failed for %d: %s"):format(userId, tostring(result)))
return not FAIL_OPEN, nil
end
if result == nil then
return false, nil
end
if typeof(result) == "table" then
return true, result :: BanRecord
end
return true, nil
end
local function kickIfBanned(player: Player)
local banned, record = readBan(player.UserId)
if not banned then
return
end
local reason = record and record.reason
local message = "You are banned from this experience."
if reason and #reason > 0 then
message ..= "\nReason: " .. reason
end
player:Kick(message)
end
Players.PlayerAdded:Connect(kickIfBanned)
for _, player in Players:GetPlayers() do
task.spawn(kickIfBanned, player)
end
task.spawn(function()
while true do
task.wait(RECHECK_SECONDS)
for _, player in Players:GetPlayers() do
task.spawn(kickIfBanned, player)
end
end
end)
Buying Pro
Store (needs a License server URL in Settings — see the go-live docs if you're the operator).
- Enter the email the license should be issued to.
- Pick a plan: Monthly or Yearly (auto-renewing subscriptions) or Lifetime (one payment).
- Buy with card opens Stripe Checkout in your browser. Pay with Litecoin (yearly / lifetime only) shows an address, an exact amount, and a QR code.
- The app polls your order and activates automatically once payment clears — card in seconds, Litecoin after a couple of confirmations.
- If you paid on another device, use Already paid? and paste the claim code from your receipt.
All sales are final — refund policy.
Licensing
- Licenses are cryptographically signed and validated offline — the app works with no internet.
- A license is bound to one machine. To move it, email support@ocstudio.xyz.
- If a License server URL is set, the app checks in on every launch to pick up renewals and revocations.
- Where everything lives:
%LOCALAPPDATA%\OpenCloudStudioSuite\— encrypted API keys (keyvault.json), OAuth tokens (oauth.bin), settings (config.json), the license (license.json), and the local history / audit / snapshot files.
Troubleshooting
| Symptom | Fix |
|---|---|
| "Key rejected (401/403)" | Wrong key, expired key, or your IP isn't in the key's allowlist. |
| A module shows a permission error | Add the API system / scope it names to your key in the Creator Dashboard. |
| Dashboard: "Set a target universe" | Fill in Universe ID / Place ID / Group IDs in Settings, then Reload. |
| Guardian ban doesn't kick anyone | DataStore name mismatch, or the game doesn't have the enforcer script, or Studio Access to API Services is off. |
| Insights snapshot is empty | The game isn't writing numbers to the telemetry DataStore yet. |
| Litecoin payment not detected | Send the exact quoted amount; wait for the required confirmations; if you overpaid or the quote expired, email support with the transaction ID. |
| Module says "locked" after buying | Click Reload in the sidebar. |
Still stuck? support@ocstudio.xyz.