Crux SDK Quickstart

Pick curl or your engine SDK. Authenticate a player, save state, and submit a leaderboard score in about two minutes. Every call on this page matches the real, current Crux API.

Get an API key in 30 seconds โ†’ Free tier: 10,000 MAU, every feature. No credit card.

Before you start (2 minutes)

  1. Sign up at crux.supercraft.host/signup/ โ€” a project and a dev environment are created for you automatically.
  2. Open your project dashboard and copy your project ID, environment ID, and publishable API key. The publishable key is safe for player clients; never substitute the secret API key in client code.
  3. Use Authorization: ApiKey YOUR_PUBLISHABLE_KEY only to log the player in. Follow-on player calls use the returned Bearer token. Server-side administration uses a separate secret key.

๐Ÿ” Coming from PlayFab

The hard part of leaving PlayFab is rarely the backend. It is the thousand call sites. crux-playfab-compat keeps them. It takes PlayFab’s request shapes, returns PlayFab’s { code, status, data } envelope, and works with callbacks or promises, so the diff is which client you construct.

npm install crux-playfab-compat

const PlayFabClientAPI = new PlayFabClientCompat(
  CruxClient.forPlayer(baseUrl, projectId, environmentId, apiKey),
);

// unchanged from your PlayFab codebase
PlayFabClientAPI.GetUserData({ Keys: ["save"] }, (res, err) => { /* ... */ });

Available for JavaScript/TypeScript, Unity (C#) and Godot (GDScript). It covers the calls an ordinary title makes every session: login, user data, title data, leaderboards, statistics, inventory, virtual currency and friends.

Anything outside that set throws a named error instead of quietly doing nothing, and it tells you what to use instead. That is deliberate: a stub that looks like it worked is worse than a failure, and on a currency call it is somebody’s money. ExecuteCloudScript is not supported. Crux does not run customer code, so that logic moves behind your own endpoint.

Full method-by-method coverage, the methods with no Crux equivalent, and the three behaviours that differ on purpose are in the migration guide.

๐ŸŒ curl - your first authenticated call

The single call that proves your key works. It mints a guest player and returns a player_id plus a player token. Works from any HTTP client, CI, or a C++ dedicated server.

# First authenticated call - mint a guest player. Proves your API key works.
curl -X POST https://crux.supercraft.host/v1/auth/anonymous \
  -H "Authorization: ApiKey YOUR_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"anonymous_id":"device-abc-123","display_name":"Ada"}'

# 200 OK
# {
#   "player_id": "7f3c1e2a-...",
#   "access_token": "eyJhbGciOi...",   # a player JWT (Bearer)
#   "token_type": "Bearer",
#   "expires_in": 86400,
#   "refresh_token": "...",
#   "refresh_token_expires_in": 2592000
# }

Now the three calls every game needs - a saved document and a leaderboard. These use the returned player token against the real nested routes:

# Values you copy once from your project dashboard:
CRUX=https://crux.supercraft.host
PROJECT=YOUR_PROJECT_ID     # project ID
ENV=YOUR_ENV_ID             # environment ID (a "dev" env is created on signup)
TOKEN=PLAYER_ACCESS_TOKEN   # access_token returned by /v1/auth/anonymous
PLAYER=7f3c1e2a-...         # the player_id returned by /v1/auth/anonymous
BOARD=YOUR_LEADERBOARD_ID   # create a leaderboard in the dashboard, copy its ID

# 1. Save a player document (JSON whose shape you own). "save" is the document key.
curl -X PUT "$CRUX/v1/projects/$PROJECT/environments/$ENV/players/$PLAYER/documents/save" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"value":{"level":4,"coins":250,"unlocks":["sword","shield"]}}'

# 1b. Read it back with GET on the same URL.
#     Reading a key you have never written returns 404, and that is the correct
#     answer, not a broken token - a bad credential is 401 or 403. Treat it as
#     "no save yet" and fall back to your defaults. The JS SDK already does this
#     for you: getPlayerDocument() returns null instead of throwing.
curl "$CRUX/v1/projects/$PROJECT/environments/$ENV/players/$PLAYER/documents/save" \
  -H "Authorization: Bearer $TOKEN"

# 2. Submit a leaderboard score
curl -X POST "$CRUX/v1/projects/$PROJECT/environments/$ENV/leaderboards/$BOARD/scores" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"player_id":"7f3c1e2a-...","score":12750}'

# 3. Read the top of the board
curl "$CRUX/v1/projects/$PROJECT/environments/$ENV/leaderboards/$BOARD/top?limit=10" \
  -H "Authorization: Bearer $TOKEN"
# [ {"player_id":"7f3c1e2a-...","score":12750,"rank":1}, ... ]

๐Ÿ“ฆ JavaScript / TypeScript - crux-sdk

Fetch-based, zero dependencies. Works in Node 18+, browsers, Electron, and Cloudflare Workers. Install: npm install crux-sdk · source on GitHub

import { CruxClient } from "crux-sdk";

// Copy projectId, environmentId, and a PUBLISHABLE key from your
// dashboard. A "dev" environment is created for you on signup.
const gsb = CruxClient.forPlayer(
  "https://crux.supercraft.host",
  "YOUR_PROJECT_ID",
  "YOUR_ENV_ID",
  "YOUR_PUBLISHABLE_KEY",
);

// 1. Create or sign in a player. (Email shown; OAuth + guest also supported.)
const auth = await gsb.registerEmail("player@example.com", "a-strong-password");
console.log("player:", auth.player_id);

// 2. Save player data - any JSON shape you like
await gsb.setPlayerDocument(auth.player_id, "save", { level: 4, coins: 250 });

// 3. Submit and read a leaderboard. leaderboardId is the board's ID
//    (create a board in the dashboard and copy its ID).
await gsb.submitScore("YOUR_LEADERBOARD_ID", auth.player_id, 12750);
const top = await gsb.getTop("YOUR_LEADERBOARD_ID", 10);
top.forEach(e => console.log("#" + e.rank + " " + e.player_id + ": " + e.score));

๐Ÿค– Godot 4 (GDScript)

Drop the addons/crux autoload into your project. Same API, idiomatic GDScript. Great for migrating a leaderboard off a shuttered Godot backend โ€” see the SilentWolf migration guide.

# Copy the addon into res://addons/crux and autoload it
# (Project -> Project Settings -> Autoload) as "Crux".

Crux.init_player("https://crux.supercraft.host",
    "YOUR_PROJECT_ID", "YOUR_ENV_ID", "YOUR_PUBLISHABLE_KEY")

# Create or sign in a player (email; OAuth + guest also supported)
var auth = await Crux.register_email("player@example.com", "a-strong-password")
print("player: ", auth.player_id)

# Save player data
await Crux.set_player_document(auth.player_id, "save", { "level": 4, "coins": 250 })

# Submit a leaderboard score (leaderboard_id is the board's ID from the dashboard)
await Crux.submit_score("YOUR_LEADERBOARD_ID", auth.player_id, 12750.0)

Want a complete project instead of snippets? Relay Zero is an open-source Godot 4 co-op demo wired to Crux end to end: an authoritative headless server that verifies player tokens, versioned live-config bundles with a last-known-good fallback, and server-owned rewards and leaderboards, with Docker deploy files included.

๐ŸŽฏ Unity (C#)

UPM package under Supercraft.Crux. async/await throughout; works in the editor, standalone, dedicated server builds, and WebGL.

using Supercraft.Crux;

// Copy projectId, environmentId, and a PUBLISHABLE key from your dashboard.
var gsb = ServerToolkitClient.ForPlayer(
    "https://crux.supercraft.host", "YOUR_PROJECT_ID", "YOUR_ENV_ID", "YOUR_PUBLISHABLE_KEY");

// Create or sign in a player
var auth = await gsb.RegisterEmailAsync("player@example.com", "a-strong-password");
Debug.Log($"player: {auth.player_id}");

// Save player data (raw JSON string)
await gsb.SetPlayerDocumentAsync(auth.player_id, "save", "{\"level\":4,\"coins\":250}");

// Submit + read a leaderboard (leaderboardId is the board's ID from the dashboard)
await gsb.SubmitScoreAsync("YOUR_LEADERBOARD_ID", auth.player_id, 12750);
var top = await gsb.GetTopAsync("YOUR_LEADERBOARD_ID", 10);
foreach (var e in top) Debug.Log($"#{e.rank} {e.player_id}: {e.score}");

๐ŸŽฎ Roblox & any other engine

Roblox talks to Crux from a ServerScriptService script over HttpService using a server token โ€” walk through it in the Roblox quickstart. Any engine that can make an HTTPS request works the same way: the API is plain HTTP + JSON, and the curl block above is the whole contract.