- Luau 99.4%
- Nix 0.6%
| .forgejo/workflows | ||
| .github | ||
| .vscode | ||
| docs | ||
| src | ||
| tests | ||
| .editorconfig | ||
| .gitignore | ||
| .luaurc | ||
| bleumoon.lock | ||
| bleumoon.toml | ||
| CHANGELOG.md | ||
| flake.lock | ||
| flake.nix | ||
| init.luau | ||
| LICENSE | ||
| README.md | ||
| stylua.toml | ||
Bleublox
A comprehensive Roblox Open Cloud API SDK for BleuMoon.
Overview
Bleublox is a fully-typed Luau SDK that wraps the Roblox Open Cloud API, built for the BleuMoon runtime. It provides a clean, idiomatic interface for managing Roblox experiences, data stores, assets, users, messaging, webhooks, and more — all from outside the Roblox engine.
Bleublox handles authentication (API Key, OAuth 2.0, Cookie, and Login with captcha solving), automatic retries with exponential backoff, rate-limit awareness, CSRF token management for legacy domains, long-running operation polling, pagination helpers, and webhook signature verification out of the box.
Features
- 29 services covering the full Roblox Open Cloud and legacy API surface area
- Four auth methods — API Key, OAuth 2.0 (client credentials), Cookie (.ROBLOSECURITY), and Login (with captcha solving & 2FA)
- Multi-domain support — per-request base URL routing for legacy *.roblox.com domains
- CSRF token management — automatic X-CSRF-TOKEN injection and rotation for legacy endpoints
- Lazy service initialization — services are only created when first accessed
- Automatic retries with exponential backoff for transient errors (429, 500, 502, 503)
- Rate-limit handling with
Retry-Afterheader support - Long-running operation polling with configurable backoff and timeout
- Pagination helpers — collect all pages or iterate page-by-page
- Webhook server with signature verification and event routing
- Strict Luau types for all requests, responses, and errors
Supported Services
| Category | Services |
|---|---|
| Experiences | Universes, Places, Team Create, Matchmaking |
| Data | DataStores, Ordered DataStores, Memory Store (Queues & Sorted Maps) |
| Messaging | Messaging (cross-server pub/sub) |
| Assets | Assets, Asset Delivery, Asset Permissions, Asset Quotas, Creator Store |
| Users | Users, Inventory, User Restrictions |
| Social | Groups, Following |
| Monetization | Game Passes, Developer Products, Subscriptions |
| Engagement | Badges, Notifications |
| Execution | Luau Execution |
| AI & Localization | Generative AI, Localization |
| Security | Secrets, API Key Introspection, OAuth Endpoints |
Installation
Using the BleuMoon CLI
bleumoon pkg add bleublox
Manual — bleumoon.toml
Add bleublox to your project dependencies:
[dependencies]
bleublox = "*"
Then install:
bleumoon pkg install
Quick Start
local Bleublox = require("@pkg/bleublox/src")
local client = Bleublox.new({
auth = {
type = "apiKey",
apiKey = "your-roblox-open-cloud-api-key",
},
})
-- Get universe info
local universe, err = client.universes.get("123456789")
if err then
warn(`Failed: {err.message}`)
else
print(`Universe: {universe.displayName}`)
end
Note: BleuMoon packages require
@pkg/bleublox/srcto import (not@pkg/bleublox) due to how packages are resolved.
Authentication
Bleublox supports four authentication methods. The auth provider is configured once when creating the client and is automatically injected into every request.
API Key
The simplest method. Create an API key in the Roblox Creator Hub.
local client = Bleublox.new({
auth = {
type = "apiKey",
apiKey = "your-api-key",
},
})
OAuth 2.0 (Client Credentials)
For apps that authenticate as themselves (not on behalf of a user). Tokens are automatically cached and refreshed 30 seconds before expiry.
local client = Bleublox.new({
auth = {
type = "oauth",
clientId = "your-client-id",
clientSecret = "your-client-secret",
tokenEndpoint = "https://apis.roblox.com/oauth/v1/token",
scopes = { "openid", "universe:read" },
},
})
Cookie (.ROBLOSECURITY)
For accessing legacy Roblox APIs that require cookie-based authentication. CSRF tokens are automatically managed.
local client = Bleublox.new({
auth = {
type = "cookie",
cookie = "your-.ROBLOSECURITY-cookie-value",
},
})
Login (Username/Password)
Full login flow with FunCaptcha solving and two-step verification support. Acquires a .ROBLOSECURITY cookie automatically.
local client = Bleublox.new({
auth = {
type = "login",
credentialType = "Username", -- or "Email" or "PhoneNumber"
credentialValue = "your-username",
password = "your-password",
captchaSolver = {
provider = "anticaptcha", -- or "2captcha", or "custom"
apiKey = "your-captcha-solver-api-key",
},
twoFactorCallback = function(mediaType: string, userId: string)
-- Return the 2FA code (e.g., from an authenticator app or prompt)
return "123456"
end,
},
})
Client Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
auth |
AuthConfig |
required | Authentication configuration |
baseUrl |
string? |
https://apis.roblox.com |
Override the API base URL |
timeout |
number? |
30 |
Request timeout in seconds |
maxRetries |
number? |
3 |
Maximum retry attempts for transient errors |
Services
All services are accessed as properties on the client. They are lazily initialized — the service object is only created the first time you access it.
Universes & Places
-- Get a universe (experience)
local universe, err = client.universes.get("universeId")
-- Update a universe
local updated, err = client.universes.update("universeId", {
displayName = "My Cool Experience",
description = "A great experience!",
})
-- Restart all servers
local ok, err = client.universes.restartServers("universeId")
-- Get a place
local place, err = client.places.get("universeId", "placeId")
-- Update a place
local updated, err = client.places.update("universeId", "placeId", {
displayName = "Main Place",
serverSize = 50,
})
DataStores
-- List data stores in a universe
local stores, nextToken, err = client.datastores.list("universeId", {
maxPageSize = 10,
})
-- Create an entry
local entry, err = client.datastores.createEntry("universeId", "datastoreId", "playerData_123", {
value = { coins = 100, level = 5 },
})
-- Get an entry
local entry, err = client.datastores.getEntry("universeId", "datastoreId", "playerData_123")
-- Update an entry
local updated, err = client.datastores.updateEntry("universeId", "datastoreId", "playerData_123", {
value = { coins = 200, level = 6 },
})
-- Increment a numeric value
local result, err = client.datastores.incrementEntry("universeId", "datastoreId", "totalVisits", 1)
-- Delete an entry
local ok, err = client.datastores.deleteEntry("universeId", "datastoreId", "playerData_123")
-- List entry revisions
local revisions, nextToken, err = client.datastores.listRevisions(
"universeId", "datastoreId", "playerData_123"
)
-- Create a snapshot of all data stores
local snapshot, err = client.datastores.snapshot("universeId")
Ordered DataStores
-- List sorted entries (e.g., leaderboard)
-- Note: requires a scopeId (e.g., "global")
local entries, nextToken, err = client.orderedDatastores.listEntries(
"universeId", "datastoreId", "global", { maxPageSize = 100 }
)
-- Create a sorted entry
local entry, err = client.orderedDatastores.createEntry(
"universeId", "datastoreId", "global", "player_1", { value = 9500 }
)
-- Get an entry
local entry, err = client.orderedDatastores.getEntry(
"universeId", "datastoreId", "global", "player_1"
)
-- Update an entry
local updated, err = client.orderedDatastores.updateEntry(
"universeId", "datastoreId", "global", "player_1", { value = 10000 }
)
-- Increment a value
local updated, err = client.orderedDatastores.incrementEntry(
"universeId", "datastoreId", "global", "player_1", 100
)
-- Delete an entry
local ok, err = client.orderedDatastores.deleteEntry(
"universeId", "datastoreId", "global", "player_1"
)
Memory Store
Memory Store supports both queues and sorted maps.
-- Flush all memory store data
local ok, err = client.memoryStore.flush("universeId")
-- === Queues ===
-- Enqueue an item
local item, err = client.memoryStore.enqueue("universeId", "queueId", {
data = '{"action":"process"}',
priority = 1,
expireTime = "2026-12-31T23:59:59Z",
})
-- Read items from a queue
local result, err = client.memoryStore.readQueue("universeId", "queueId", 5)
-- Discard (acknowledge) processed items
local ok, err = client.memoryStore.discardQueueItems("universeId", "queueId", result.readId)
-- === Sorted Maps ===
-- Create a sorted map item
local item, err = client.memoryStore.createSortedMapItem("universeId", "sortedMapId", {
id = "player_1",
value = '{"score":100}',
numericSortKey = 100,
})
-- Get a sorted map item
local item, err = client.memoryStore.getSortedMapItem("universeId", "sortedMapId", "player_1")
-- List sorted map items
local items, nextToken, err = client.memoryStore.listSortedMapItems("universeId", "sortedMapId")
Messaging
-- Publish a cross-server message
local ok, err = client.messaging.publish("universeId", "announcements", "Server restarting!")
-- Also available directly on universes
local ok, err = client.universes.publishMessage("universeId", "announcements", "Hello world!")
Assets
local fs = require("@bleumoon/fs")
-- Create an asset (multipart upload with async operation polling)
local fileContent = fs.readFile("model.fbx")
local asset, err = client.assets.create({
assetType = "Model",
displayName = "My Model",
description = "A cool model",
creationContext = {
creator = { userId = "123456" },
},
}, fileContent, "model/fbx")
-- Get an asset
local asset, err = client.assets.get("assetId")
-- Update asset metadata
local updated, err = client.assets.update("assetId", {
displayName = "Updated Name",
})
-- Archive / Restore an asset
local archived, err = client.assets.archive("assetId")
local restored, err = client.assets.restore("assetId")
-- List asset versions
local versions, nextToken, err = client.assets.listVersions("assetId")
-- Get a specific asset version
local version, err = client.assets.getVersion("assetId", "versionNumber")
-- Rollback to a previous version
local rolledBack, err = client.assets.rollbackVersion("assetId", "versionNumber")
Asset Delivery
-- Get asset content by ID
local content, err = client.assetDelivery.get("assetId")
-- Get asset content for a specific version
local content, err = client.assetDelivery.getVersion("assetId", "versionNumber")
Users & Inventory
-- Get user info
local user, err = client.users.get("userId")
print(user.displayName, user.name)
-- Generate a user thumbnail (async with polling)
local thumbnail, err = client.users.generateThumbnail("userId", {
size = "420x420",
format = "Png",
shape = "Round",
})
-- List user inventory
local items, nextToken, err = client.inventory.list("userId", {
maxPageSize = 25,
})
Groups
-- Get group info
local group, err = client.groups.get("groupId")
-- List group roles
local roles, nextToken, err = client.groups.listRoles("groupId")
-- Get a specific role
local role, err = client.groups.getRole("groupId", "roleId")
-- Get group shout
local shout, err = client.groups.getShout("groupId")
-- List group memberships (uses wildcard group; filter with options.filter)
local members, nextToken, err = client.groups.listMemberships({
filter = 'user == "users/123456"',
})
-- Update a member's role
local membership, err = client.groups.updateMembership("groupId", "membershipId", {
role = "groups/123/roles/456",
})
-- List join requests
local requests, nextToken, err = client.groups.listJoinRequests("groupId")
-- Accept / decline join requests
local ok, err = client.groups.acceptJoinRequest("groupId", "joinRequestId")
local ok, err = client.groups.declineJoinRequest("groupId", "joinRequestId")
-- Group forums
local categories, nextToken, err = client.groups.listForumCategories("groupId")
local posts, nextToken, err = client.groups.listForumPosts("groupId", "categoryId")
local comments, nextToken, err = client.groups.listForumComments("groupId", "categoryId", "postId")
Notifications & Subscriptions
-- Send a notification to a user
local ok, err = client.notifications.send("userId", {
source = { universe = "universes/123456" },
payload = {
messageId = "welcome-back",
type = "MOMENT",
},
})
-- Get a specific subscription
local sub, err = client.subscriptions.get("universeId", "productId", "subscriptionId")
Game Passes & Developer Products
-- List game passes for a universe
local passes, nextToken, err = client.gamePasses.list("universeId")
-- Create a game pass
local pass, err = client.gamePasses.create("universeId", {
displayName = "VIP Pass",
description = "Access VIP areas",
price = 100,
})
-- List developer products
local products, nextToken, err = client.developerProducts.list("universeId")
-- Create a developer product
local product, err = client.developerProducts.create("universeId", {
displayName = "100 Coins",
description = "Purchase 100 coins",
price = 50,
})
Luau Execution
-- Upload binary input for a task
local input, err = client.luauExecution.uploadBinaryInput("universeId", binaryData)
-- Execute a Luau script on a place's server
local task, err = client.luauExecution.createTask("universeId", "placeId", {
script = 'return "Hello from the cloud!"',
})
-- Execute on a specific place version
local task, err = client.luauExecution.createTaskForVersion(
"universeId", "placeId", "versionId", { script = 'return 42' }
)
-- Get task status (requires full path: universeId, placeId, versionId, sessionId, taskId)
local taskInfo, err = client.luauExecution.getTask(
"universeId", "placeId", "versionId", "sessionId", "taskId"
)
-- Get execution logs
local logs, err = client.luauExecution.getTaskLogs(
"universeId", "placeId", "versionId", "sessionId", "taskId"
)
Badges
-- Create a badge
local badge, err = client.badges.create("universeId", {
displayName = "First Win",
description = "Win your first game",
iconAssetId = "123456",
})
-- Update a badge
local updated, err = client.badges.update("badgeId", {
displayName = "Updated Badge Name",
enabled = true,
})
-- Update a badge icon
local ok, err = client.badges.updateIcon("badgeId", iconData, "image/png")
Following
-- List universes a user follows
local universes, nextToken, err = client.following.list("userId")
-- Check if a user follows a universe
local status, err = client.following.getStatus("userId", "universeId")
-- Follow / unfollow a universe
local ok, err = client.following.follow("userId", "universeId")
local ok, err = client.following.unfollow("userId", "universeId")
Secrets
-- List secrets
local secrets, nextToken, err = client.secrets.list("universeId")
-- Create a secret
local secret, err = client.secrets.create("universeId", {
secretId = "my-secret",
value = "super-secret-value",
})
-- Update a secret
local updated, err = client.secrets.update("universeId", "secretId", {
value = "new-secret-value",
})
-- Delete a secret
local ok, err = client.secrets.delete("universeId", "secretId")
-- Get the public key for secret encryption
local publicKey, err = client.secrets.getPublicKey("universeId")
User Restrictions
-- List user restrictions for a universe
local restrictions, nextToken, err = client.userRestrictions.list("universeId")
-- Get a specific user restriction (universe-level)
local restriction, err = client.userRestrictions.get("universeId", "userRestrictionId")
-- Ban/restrict a user (universe-level)
local updated, err = client.userRestrictions.update("universeId", "userRestrictionId", {
gameJoinRestriction = {
active = true,
duration = "3600s",
privateReason = "Exploiting",
displayReason = "Violated community guidelines",
},
})
-- List restriction logs for a universe
local logs, nextToken, err = client.userRestrictions.listLogs("universeId")
-- Place-level restrictions (more granular)
local restrictions, nextToken, err = client.userRestrictions.listForPlace("universeId", "placeId")
local restriction, err = client.userRestrictions.getForPlace("universeId", "placeId", "userRestrictionId")
local updated, err = client.userRestrictions.updateForPlace("universeId", "placeId", "userRestrictionId", {
gameJoinRestriction = {
active = true,
duration = "3600s",
privateReason = "Exploiting",
displayReason = "Violated community guidelines",
},
})
Places (Extended)
-- Publish a place file
local result, err = client.places.publish("universeId", "placeId", fileContent, "Published")
-- Get a specific instance in a place
local instance, err = client.places.getInstance("universeId", "placeId", "instanceId")
-- List children of an instance
local children, nextToken, err = client.places.listInstanceChildren("universeId", "placeId", "instanceId")
-- Update an instance
local result, err = client.places.updateInstance("universeId", "placeId", "instanceId", {
engineInstance = { ... },
})
Asset Permissions
-- Update asset permissions
local ok, err = client.assetPermissions.update({
permissions = {
{ assetId = "123", action = "Use", subjectType = "User", subjectId = "456" },
},
})
Asset Quotas
-- List asset quotas for a user
local quotas, nextToken, err = client.assetQuotas.list("userId")
Creator Store
-- Search the Creator Store
local results, nextToken, err = client.creatorStore.search("sword model")
-- Get a specific asset from the store
local asset, err = client.creatorStore.getAsset("assetId")
-- Create a product
local product, err = client.creatorStore.createProduct({
displayName = "My Asset Pack",
description = "A collection of assets",
basePrice = 100,
})
-- Get / update a product
local product, err = client.creatorStore.getProduct("productId")
local updated, err = client.creatorStore.updateProduct("productId", {
displayName = "Updated Name",
})
-- Manage saved items
local saves, nextToken, err = client.creatorStore.getSaves()
local save, err = client.creatorStore.createSave({ assetId = "123" })
local ok, err = client.creatorStore.deleteSave("saveId")
local ok, err = client.creatorStore.bulkDeleteSaves({ "saveId1", "saveId2" })
Generative AI
-- Generate speech from text
local result, err = client.generativeAI.generateSpeechAsset("universeId", {
text = "Hello, world!",
voiceId = "default",
})
-- Translate text
local result, err = client.generativeAI.translateText("universeId", {
texts = { "Hello", "Goodbye" },
targetLanguage = "es",
})
-- Evaluate Luau code in a sandbox
local result, err = client.generativeAI.eval({ source = 'return 1 + 1' })
local record, err = client.generativeAI.getEvalRecord("jobId")
Team Create
-- Get Team Create settings
local settings, err = client.teamCreate.getSettings("universeId")
-- Update Team Create settings
local updated, err = client.teamCreate.updateSettings("universeId", { isEnabled = true })
-- List active Team Create members
local members, err = client.teamCreate.listActiveMembers("placeId")
-- Close a Team Test session
local result, err = client.teamCreate.closeTeamTest("universeId", "placeId")
-- Remove a member from Team Create
local result, err = client.teamCreate.removeMember("universeId", "userId")
-- Get settings for multiple universes at once
local settings, err = client.teamCreate.multigetSettings({ "universeId1", "universeId2" })
Matchmaking
-- Get/set client matchmaking status
local status, err = client.matchmaking.getClientStatus("universeId", "clientId")
local result, err = client.matchmaking.setClientStatus("universeId", "clientId", { status = "ready" })
-- Forecast and launch fleet updates
local forecast, err = client.matchmaking.forecastUpdate("universeId", {
universeId = "universeId",
placeId = "placeId",
})
local updateStatus, err = client.matchmaking.getUpdateStatus("universeId", "updateId")
local launched, err = client.matchmaking.launchUpdate("universeId", {
universeId = "universeId",
placeId = "placeId",
})
-- Shutdown servers
local ok, err = client.matchmaking.shutdown("universeId", "serverId")
local ok, err = client.matchmaking.shutdownAll("universeId")
Localization
-- List localization tables for a game
local tables, err = client.localization.listTables("gameId")
-- Get/create/update a localization table
local tbl, err = client.localization.getTable("tableId")
local created, err = client.localization.createTable({ name = "MyTable" })
local updated, err = client.localization.updateTable("tableId", { name = "Renamed" })
-- Get/update table entries
local entries, err = client.localization.getTableEntries("tableId")
local result, err = client.localization.updateTableEntries("tableId", { ... })
-- Auto-localization
local settings, err = client.localization.getAutoLocalizationSettings("gameId")
local updated, err = client.localization.updateAutoLocalizationSettings("gameId", { isAutoLocalizationEnabled = true })
-- Supported languages
local langs, err = client.localization.getSupportedLanguages("gameId")
-- Source language
local lang, err = client.localization.getSourceLanguage("gameId")
local result, err = client.localization.setSourceLanguage("gameId", "en")
-- Name & description translations
local translations, err = client.localization.getNameDescriptionTranslations("gameId")
-- Badge / GamePass / DevProduct translations
local translations, err = client.localization.getBadgeTranslations("badgeId")
local translations, err = client.localization.getGamePassTranslations("gamePassId")
local translations, err = client.localization.getDeveloperProductTranslations("devProductId")
OAuth Endpoints & API Key Introspection
-- Get info about the OAuth user (requires OAuth auth)
local userInfo, err = client.oauth.getUserInfo()
-- Get token resource info
local resources, err = client.oauth.getTokenResources()
-- Introspect the current API key (requires API Key auth)
local keyInfo, err = client.apiKeys.introspect()
Webhooks
Bleublox includes a built-in webhook server that listens for Roblox webhook notifications, verifies signatures, and routes events to handlers.
local Bleublox = require("@pkg/bleublox/src")
-- Create a webhook server
local server = Bleublox.createWebhookServer({
port = 8080,
path = "/webhook", -- endpoint path (default: "/webhook")
secret = "your-webhook-secret", -- for signature verification
})
-- Register event handlers
server:on(Bleublox.WebhookEventTypes.RIGHT_TO_ERASURE, function(event)
print(`Right to erasure request: {event.notificationId}`)
-- Handle GDPR deletion request
end)
server:on(Bleublox.WebhookEventTypes.SUBSCRIPTION_PURCHASED, function(event)
print(`New subscription: {event.eventPayload}`)
end)
-- Start listening
server:start()
print("Webhook server running on port 8080")
Available Event Types
| Constant | Event Type |
|---|---|
SAMPLE_NOTIFICATION |
SampleNotification |
RIGHT_TO_ERASURE |
RightToErasureRequest |
SUBSCRIPTION_PURCHASED |
SubscriptionPurchased |
SUBSCRIPTION_RENEWED |
SubscriptionRenewed |
SUBSCRIPTION_RESUBSCRIBED |
SubscriptionResubscribed |
SUBSCRIPTION_REFUNDED |
SubscriptionRefunded |
SUBSCRIPTION_CANCELLED |
SubscriptionCancelled |
COMMERCE_PRODUCT_ORDER_PAID |
CommerceProductOrderPaid |
COMMERCE_PRODUCT_ORDER_REFUNDED |
CommerceProductOrderRefunded |
Pagination
Many list endpoints return paginated results. Bleublox provides two helpers via the pagination utility:
Collect All Pages
Automatically fetches every page and returns a single flat array:
local Pagination = require("@pkg/bleublox/src/utils/pagination")
local allStores = Pagination.collectAll(function(pageToken)
local stores, nextToken, err = client.datastores.list("universeId", {
maxPageSize = 100,
pageToken = pageToken,
})
return stores, nextToken
end)
print(`Total data stores: {#allStores}`)
Page-by-Page Iterator
Iterate one page at a time for memory-efficient processing:
local Pagination = require("@pkg/bleublox/src/utils/pagination")
for items in Pagination.pages(function(pageToken)
local entries, nextToken, err = client.datastores.listEntries("universeId", "storeId", {
maxPageSize = 50,
pageToken = pageToken,
})
return entries, nextToken
end) do
for _, entry in items do
print(entry.id, entry.value)
end
end
Error Handling
All service methods return (result?, BleubloxError?) tuples. A nil first value with a non-nil error indicates failure.
BleubloxError Structure
type BleubloxError = {
code: string, -- Error code (e.g., "NOT_FOUND", "NETWORK_ERROR", "TIMEOUT")
message: string, -- Human-readable error description
statusCode: number?, -- HTTP status code (nil for non-HTTP errors)
details: { any }?, -- Additional error details from the API
retryable: boolean, -- Whether this error is safe to retry
}
Handling Errors
local universe, err = client.universes.get("12345")
if err then
if err.retryable then
-- Transient error — Bleublox already retried maxRetries times
warn(`Transient failure after retries: {err.message}`)
elseif err.statusCode == 404 then
print("Universe not found")
else
error(`API error [{err.code}]: {err.message} (HTTP {err.statusCode})`)
end
else
print(`Got universe: {universe.displayName}`)
end
Automatic Retries
The HTTP client automatically retries on:
- 429 — Rate limited (respects
Retry-Afterheader) - 500 — Internal server error
- 502 — Bad gateway
- 503 — Service unavailable
Retries use exponential backoff (1s → 2s → 4s → ..., capped at 30s) up to maxRetries (default: 3).
Long-Running Operations
Some endpoints (asset creation, thumbnail generation) return Operations that must be polled. Bleublox handles polling automatically within service methods like assets.create() and users.generateThumbnail(). The polling behavior uses exponential backoff with configurable options (initial delay, max delay, backoff multiplier, and timeout).
The Operations.poll() utility is used internally by the HTTP client. You typically don't need to call it directly — just call the service method and the result is returned when the operation completes.
Project Structure
├── bleumoon.toml # Package manifest & dependencies
├── src/
│ ├── init.luau # Main entry point — Bleublox.new()
│ ├── client.luau # HTTP client with auth, retries, rate-limiting, CSRF
│ ├── csrf.luau # CSRF token manager for legacy Roblox domains
│ ├── auth/
│ │ ├── init.luau # Auth provider factory (API Key, OAuth 2.0, Cookie, Login)
│ │ ├── captcha.luau # FunCaptcha solver (Anti-Captcha, 2Captcha, custom)
│ │ └── login.luau # Roblox login flow with captcha & 2FA
│ ├── services/
│ │ ├── init.luau # Service index (29 services)
│ │ ├── universes.luau # Universes service
│ │ ├── places.luau # Places service
│ │ ├── datastores.luau # Standard DataStores service
│ │ ├── orderedDatastores.luau
│ │ ├── memoryStore.luau # Memory Store (queues & sorted maps)
│ │ ├── messaging.luau # Cross-server messaging
│ │ ├── assets.luau # Asset management
│ │ ├── assetDelivery.luau # Asset content retrieval
│ │ ├── users.luau # User information
│ │ ├── inventory.luau # User inventory
│ │ ├── groups.luau # Group management & forums
│ │ ├── notifications.luau # Push notifications
│ │ ├── subscriptions.luau # Subscriptions
│ │ ├── gamePasses.luau # Game Passes
│ │ ├── developerProducts.luau
│ │ ├── luauExecution.luau # Remote Luau execution
│ │ ├── badges.luau # Badges
│ │ ├── following.luau # Following status
│ │ ├── secrets.luau # Secrets management
│ │ ├── userRestrictions.luau
│ │ ├── assetPermissions.luau
│ │ ├── assetQuotas.luau
│ │ ├── creatorStore.luau # Creator Store products & saves
│ │ ├── generativeAI.luau # Speech generation, translation, eval
│ │ ├── teamCreate.luau # Team Create session management
│ │ ├── matchmaking.luau # Matchmaking & fleet management
│ │ ├── localization.luau # Game localization & i18n
│ │ ├── oauthEndpoints.luau
│ │ └── apiKeyIntrospection.luau
│ ├── types/
│ │ └── init.luau # All type definitions
│ ├── utils/
│ │ ├── errors.luau # Error construction & formatting
│ │ ├── operations.luau # Long-running operation polling
│ │ └── pagination.luau # Pagination helpers
│ └── webhook/
│ ├── init.luau # Webhook server factory
│ ├── events.luau # Event types & router
│ └── signatures.luau # Signature verification
├── tests/ # Test suite (BleuTest)
└── docs/
├── architecture/ # Architecture Decision Records
├── guides/ # User-facing guides
└── investigations/ # Bug investigation write-ups
Prerequisites
Tip: If you have Nix with flakes enabled, dev tools are provided automatically via
nix develop.
Running Tests
bleumoon run tests/init
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-feature) - Install dependencies:
bleumoon pkg install - Make your changes with appropriate tests
- Run the test suite:
bleumoon run tests/init - Format your code:
stylua . - Update
CHANGELOG.mdand bump the version inbleumoon.tomlper semver - Open a pull request
License
This project is licensed under the MIT License.