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. It's a plain .NET desktop app;
full VirusTotal report.
Confirm the download matches what we published:
certutil -hashfile <installer> SHA256→bd2870445d858ed40ce42ea528f8c119fa4671921f87329400108a45487bf833. - 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
Open the Store tab.
- 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.
- The app checks in with the license server on every launch and every few hours to pick up renewals and revocations. If it can't reach the server, the cached license keeps working until it expires.
- 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.