A powerful Roblox library, built for PigBot.
  • Luau 99.3%
  • Nix 0.7%
Find a file
2026-02-08 20:01:29 -06:00
.github initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
docs initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
src initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
tests initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
.editorconfig initial setup 2026-02-08 18:24:54 -06:00
.env.example initial setup 2026-02-08 18:24:54 -06:00
.envrc initial setup 2026-02-08 18:24:54 -06:00
.gitignore initial setup 2026-02-08 18:24:54 -06:00
.luaurc initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
COMMANDS.md initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
flake.lock initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
flake.nix initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
README.md initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00
ROBLOX_OPEN_CLOUD_API_REFERENCE.md initial implementation of BleuBlox 2026-02-08 20:01:29 -06:00

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-After header support
  • JSON serialization/deserialization handled automatically
  • Pagination helpers for listing endpoints
  • Type-safe Luau with --!strict throughout
  • 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