A powerful Roblox library, built for PigBot.
- Luau 99.3%
- Nix 0.7%
| .github | ||
| docs | ||
| src | ||
| tests | ||
| .editorconfig | ||
| .env.example | ||
| .envrc | ||
| .gitignore | ||
| .luaurc | ||
| COMMANDS.md | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| ROBLOX_OPEN_CLOUD_API_REFERENCE.md | ||
BleuBlox
A comprehensive Roblox Open Cloud API library for the BleuMoon (Lune) runtime.
Features
- 15 API modules covering the full Roblox Open Cloud surface
- Automatic retry with exponential backoff for rate-limited and server-error responses
- Built-in
Retry-Afterheader support - JSON serialization/deserialization handled automatically
- Pagination helpers for listing endpoints
- Type-safe Luau with
--!strictthroughout - 93 unit tests with a custom test framework and mock HTTP client
Prerequisites
- Nix with flakes enabled (provides BleuMoon runtime)
- A Roblox Open Cloud API key (create one here)
Quick Start
# Enter the development environment
nix develop
# Run the example
lune run src/main
# Run tests
lune run tests/run_all
Basic Usage
local BleuBlox = require("@src/init")
-- Create a client with your API key
local client = BleuBlox.new("your-api-key-here")
-- Or load from an environment variable (defaults to ROBLOX_API_KEY)
local client = BleuBlox.fromEnv()
-- Fetch a user
local response = client.Users:getUser("12345")
if response.ok then
print(response.body.displayName)
end
-- List DataStores
local stores = client.DataStores:listDataStores("UNIVERSE_ID")
-- Ban a user
client.UserRestrictions:banUser("UNIVERSE_ID", "USER_ID", {
duration = "3600s",
displayReason = "Rule violation",
privateReason = "Exploiting",
})
Configuration
local client = BleuBlox.new("api-key", {
baseUrl = "https://apis.roblox.com", -- default
maxRetries = 3, -- default
retryDelay = 1, -- seconds, default
timeout = 30, -- seconds, default
userAgent = "BleuBlox/1.0.0 (BleuMoon/Lune)", -- default
})
Project Structure
.
├── src/
│ ├── init.luau # Library entry point (BleuBlox.new / BleuBlox.fromEnv)
│ ├── main.luau # Example usage script
│ ├── core/
│ │ ├── Config.luau # Configuration defaults and merging
│ │ ├── HttpClient.luau # HTTP client with auth, retries, rate-limiting
│ │ └── Utils.luau # Shared utilities (pagination, query building, etc.)
│ └── apis/
│ ├── Assets.luau # Asset management (create, update, versions, archive)
│ ├── Badges.luau # Badge CRUD and awarding
│ ├── DataStores.luau # Standard DataStores (CRUD, versions, increment)
│ ├── Experiences.luau # Universe management and secrets
│ ├── GamePasses.luau # Game pass CRUD
│ ├── Groups.luau # Group info, memberships, roles, join requests, shout
│ ├── Inventory.luau # User inventory listing
│ ├── MemoryStores.luau # Sorted maps and queues
│ ├── Messaging.luau # MessagingService topic publishing
│ ├── Notifications.luau # User notification sending
│ ├── OrderedDataStores.luau # Ordered DataStore entries
│ ├── Places.luau # Place management, publishing, Luau execution
│ ├── Subscriptions.luau # Subscription listing
│ ├── UserRestrictions.luau # User bans (universe and place level)
│ └── Users.luau # User info, thumbnails, search
├── tests/
│ ├── run_all.luau # Test runner entry point
│ ├── framework.luau # Custom test framework (describe/it/expect)
│ ├── MockHttpClient.luau # Mock HTTP client for unit testing
│ ├── test_core.luau # Tests for Config, Utils
│ ├── test_datastores.luau # Tests for DataStores API
│ ├── test_data_services.luau # Tests for OrderedDataStores, MemoryStores, Messaging
│ └── test_apis.luau # Tests for all remaining API modules
└── docs/
└── architecture/ # Architecture decision records
API Reference
All API methods return an ApiResponse table:
type ApiResponse = {
ok: boolean, -- true if HTTP 2xx
statusCode: number, -- HTTP status code
statusMessage: string, -- HTTP status message
headers: { [string]: string },
body: any, -- Parsed JSON (or raw string)
rawBody: string, -- Raw response body
}
DataStores
client.DataStores:listDataStores(universeId, { cursor?, limit?, prefix? })
client.DataStores:listEntries(universeId, datastoreName, { scope?, allScopes?, prefix?, cursor?, limit? })
client.DataStores:getEntry(universeId, datastoreName, entryKey, { scope? })
client.DataStores:setEntry(universeId, datastoreName, entryKey, value, { scope?, matchVersion?, exclusiveCreate?, userIds?, attributes? })
client.DataStores:incrementEntry(universeId, datastoreName, entryKey, incrementBy, { scope?, userIds?, attributes? })
client.DataStores:deleteEntry(universeId, datastoreName, entryKey, { scope? })
client.DataStores:listEntryVersions(universeId, datastoreName, entryKey, { scope?, cursor?, limit?, startTime?, endTime?, sortOrder? })
client.DataStores:getEntryVersion(universeId, datastoreName, entryKey, versionId, { scope? })
client.DataStores:listAllEntries(universeId, datastoreName, options?) -- auto-paginates
OrderedDataStores
client.OrderedDataStores:listEntries(universeId, datastoreName, { scope?, pageSize?, pageToken?, orderBy?, filter? })
client.OrderedDataStores:createEntry(universeId, datastoreName, entryId, value)
client.OrderedDataStores:getEntry(universeId, datastoreName, entryId, { scope? })
client.OrderedDataStores:updateEntry(universeId, datastoreName, entryId, value, { scope?, allowMissing? })
client.OrderedDataStores:incrementEntry(universeId, datastoreName, entryId, incrementBy, { scope? })
client.OrderedDataStores:deleteEntry(universeId, datastoreName, entryId, { scope? })
MemoryStores
-- Sorted Maps
client.MemoryStores:getSortedMapItem(universeId, sortedMapName, itemId)
client.MemoryStores:setSortedMapItem(universeId, sortedMapName, itemId, value, { ttl?, numericSortKey?, stringSortKey?, etag? })
client.MemoryStores:deleteSortedMapItem(universeId, sortedMapName, itemId, etag?)
client.MemoryStores:listSortedMapItems(universeId, sortedMapName, { maxPageSize?, orderBy?, filter?, pageToken? })
-- Queues
client.MemoryStores:enqueueItem(universeId, queueName, value, { ttl?, priority? })
client.MemoryStores:dequeueItem(universeId, queueName, count?, { allOrNothing? })
client.MemoryStores:readQueueItems(universeId, queueName, count?, { allOrNothing? })
client.MemoryStores:flushQueue(universeId, queueName)
Messaging
client.Messaging:publish(universeId, topic, message)
-- topic: max 80 chars, message: max 1KB
Assets
client.Assets:createAsset({ assetType, displayName, description, creationContext, fileContent, contentType? })
client.Assets:getAsset(assetId)
client.Assets:updateAsset(assetId, { displayName?, description? })
client.Assets:getAssetVersion(assetId, versionNumber)
client.Assets:listAssetVersions(assetId, { maxPageSize?, pageToken? })
client.Assets:rollbackAssetVersion(assetId, versionNumber)
client.Assets:archiveAsset(assetId)
client.Assets:restoreAsset(assetId)
client.Assets:getOperation(operationId)
Experiences
client.Experiences:getUniverse(universeId)
client.Experiences:updateUniverse(universeId, updates, updateMask?)
client.Experiences:restartServers(universeId)
client.Experiences:listSecrets(universeId, { maxPageSize?, pageToken? })
client.Experiences:getSecret(universeId, secretName)
client.Experiences:createSecret(universeId, secretId, value)
client.Experiences:updateSecret(universeId, secretName, value)
client.Experiences:deleteSecret(universeId, secretName)
Places
client.Places:getPlace(universeId, placeId)
client.Places:updatePlace(universeId, placeId, updates, updateMask?)
client.Places:publishPlace(universeId, placeId, fileContent, versionType?)
client.Places:listInstances(universeId, placeId, { maxPageSize?, pageToken? })
client.Places:getInstance(universeId, placeId, instanceId)
client.Places:updateInstance(universeId, placeId, instanceId, updates, updateMask?)
client.Places:executeLuau(universeId, placeId, script)
client.Places:getExecutionTask(universeId, placeId, taskId)
client.Places:getExecutionLogs(universeId, placeId, taskId, { maxPageSize?, pageToken? })
Users
client.Users:getUser(userId)
client.Users:generateThumbnail(userId, { size?, format?, shape? })
client.Users:searchUsers(keyword, { maxPageSize?, pageToken? })
Groups
client.Groups:getGroup(groupId)
client.Groups:listMemberships(groupId, { maxPageSize?, pageToken?, filter? })
client.Groups:getMembership(groupId, membershipId)
client.Groups:updateMembership(groupId, membershipId, roleId)
client.Groups:listRoles(groupId, { maxPageSize?, pageToken? })
client.Groups:listJoinRequests(groupId, { maxPageSize?, pageToken?, filter? })
client.Groups:acceptJoinRequest(groupId, joinRequestId)
client.Groups:declineJoinRequest(groupId, joinRequestId)
client.Groups:getShout(groupId)
client.Groups:updateShout(groupId, message)
Badges
client.Badges:createBadge(universeId, { displayName, description?, enabled? })
client.Badges:getBadge(badgeId)
client.Badges:updateBadge(badgeId, updates, updateMask?)
client.Badges:listBadges(universeId, { maxPageSize?, pageToken?, filter? })
client.Badges:awardBadge(badgeId, userId)
client.Badges:removeBadgeAward(badgeId, userId)
GamePasses
client.GamePasses:createGamePass(universeId, { displayName, description?, price? })
client.GamePasses:getGamePass(universeId, gamePassId)
client.GamePasses:updateGamePass(universeId, gamePassId, updates, updateMask?)
client.GamePasses:listGamePasses(universeId, { maxPageSize?, pageToken? })
Inventory
client.Inventory:listItems(userId, { maxPageSize?, pageToken?, filter? })
client.Inventory:listAllItems(userId, options?) -- auto-paginates
Subscriptions
client.Subscriptions:getSubscription(universeId, subscriptionId)
client.Subscriptions:listSubscriptions(universeId, { maxPageSize?, pageToken? })
UserRestrictions
-- Universe-level bans
client.UserRestrictions:getUserRestriction(universeId, userId)
client.UserRestrictions:banUser(universeId, userId, { duration?, displayReason?, privateReason?, excludeAltAccounts?, idempotencyKey? })
client.UserRestrictions:unbanUser(universeId, userId)
client.UserRestrictions:listRestrictionLogs(universeId, { maxPageSize?, pageToken?, filter? })
-- Place-level bans
client.UserRestrictions:getPlaceUserRestriction(universeId, placeId, userId)
client.UserRestrictions:banUserFromPlace(universeId, placeId, userId, banOptions)
client.UserRestrictions:unbanUserFromPlace(universeId, placeId, userId)
Notifications
client.Notifications:sendNotification(userId, {
universeId = "...",
messageId = "...",
parameters = { playerName = { stringValue = "..." } },
launchData = "...", -- optional
analyticsCategory = "...", -- optional
})
Error Handling
All API methods return a response table — they never throw on HTTP errors. Check response.ok for success:
local response = client.Users:getUser("12345")
if response.ok then
print("User:", response.body.displayName)
else
print("Error:", response.statusCode, response.body)
end
For convenience, Utils.assertOk will throw an error for non-2xx responses:
local Utils = require("@src/core/Utils")
local response = client.Users:getUser("12345")
Utils.assertOk(response, "Get user") -- throws if not ok
Pagination
Use the built-in pagination helpers for listing endpoints:
local Utils = require("@src/core/Utils")
-- Iterate page by page
for pageIndex, body in Utils.paginate(function(pageToken)
return client.DataStores:listDataStores(universeId, { cursor = pageToken })
end) do
for _, store in body.datastores do
print(store.name)
end
end
-- Or collect everything at once
local allEntries = client.DataStores:listAllEntries(universeId, "MyStore")
Testing
# Run all tests
lune run tests/run_all
The project includes a custom test framework with describe/it/expect semantics and a mock HTTP client for testing without real API calls.
License
TBD