OpenCloud Studio Suite

Documentation

Using OpenCloud Studio Suite

A complete walkthrough — install, connect, and use each module.

Install

  1. Download the installer from ocstudio.xyz and run it.
  2. 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.
  3. 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:

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).

  1. Enter the email the license should be issued to.
  2. Pick a plan: Monthly or Yearly (auto-renewing subscriptions) or Lifetime (one payment).
  3. 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.
  4. The app polls your order and activates automatically once payment clears — card in seconds, Litecoin after a couple of confirmations.
  5. If you paid on another device, use Already paid? and paste the claim code from your receipt.

All sales are finalrefund policy.

Licensing

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.