CAPYBARAStart free

SDK docs

One facade for players, saves, and rooms

Import sdk, call a method, done. Save and multiplayer calls sign the player in as a guest automatically when nobody is logged in.

Setup

Load the client, set the game id, copy the sdk/ modules (Core, Auth, Save, Multiplayer, types, index), then bootstrap at boot. Every call lazy-initializes from window.gameId when needed.

<!-- index.html -->
<script src="https://assets.capybara.build/js/game-api-client.js"></script>
<script>window.gameId = "your-game-id";</script>

// main.ts — analytics on by default, never throws into gameplay
import { enableAnalyticsByDefault } from "./sdk";
void enableAnalyticsByDefault();

// Optional: eager init with your publishable key (project page)
import { sdk } from "./sdk";
sdk.init({ publishableKey: "capy_pk_..." });

Auth

Guest accounts for instant play, passwordless email codes for returning players. Sessions persist in the browser automatically.

sdk.auth.loginAsGuest() → Promise<User>

Returns the existing user when already signed in, otherwise creates an anonymous guest.

sdk.auth.sendLoginEmail(email) → Promise<void>

Sends a one-time code to the player's inbox.

sdk.auth.verifyLoginEmail(email, otp, name?) → Promise<User>

Verifies the code and signs the player in. Optional display name for new accounts.

sdk.auth.getCurrentUser() → Promise<User | null>

The session user, or null when logged out or expired.

sdk.auth.ensureGuestSession() → Promise<User>

Current user, or a fresh guest. Used internally before saves and room calls.

sdk.auth.logout() → Promise<void>

Signs out and clears the session token.

const guest = await sdk.auth.loginAsGuest();

await sdk.auth.sendLoginEmail("ada@example.com");
const user = await sdk.auth.verifyLoginEmail(
  "ada@example.com",
  codeFromInbox,
  "Ada",
);

Saves & storage

Three scopes: one JSON save document per player, world state shared by every player, and per-player key/value slots. Missing data resolves to null, never an error.

sdk.save.saveGameData(data) → Promise<void>

Upserts the player's save document. Any JSON-serializable object.

sdk.save.loadGameData() → Promise<Record | null>

The player's save data, or null when they have no save yet.

sdk.save.saveSharedState(data) → Promise<void>

World state visible to every player of the game.

sdk.save.loadSharedState() → Promise<Record | null>

The shared world state, or null when unset.

sdk.save.setStorage(key, value) → Promise<void>

One isolated key/value slot scoped to this player and game.

sdk.save.getStorage(key) → Promise<T | null>

Reads a slot back, or null when the key does not exist.

sdk.save.deleteStorage(key) → Promise<void>

Deletes a slot.

await sdk.save.saveGameData({ level: 7, coins: 1200 });
const data = await sdk.save.loadGameData();
if (!data) showNewGameIntro();

await sdk.save.setStorage("settings", { muted: true });
const settings = await sdk.save.getStorage("settings");

Multiplayer

Join a room, then read and replace its shared state. Version tracking is automatic — a conflict raises a State conflict error, in which case refetch and retry. Room calls throw until joined.

sdk.multiplayer.joinRoom(roomId, metadata?) → Promise<state>

Joins the room and returns its current shared state. Metadata (e.g. displayName) is visible to other players.

sdk.multiplayer.getRoomState() → Promise<state>

The latest shared state; also refreshes the internal version tracker.

sdk.multiplayer.updateRoomState(newState) → Promise<state>

Replaces the room state with optimistic concurrency handled for you.

sdk.multiplayer.getRoomPlayers() → Promise<PresenceEntry[]>

Everyone in the room, with userId, joinedAt, and metadata.

sdk.multiplayer.leaveRoom() → Promise<void>

Leaves the current room. Safe to call when not in one.

await sdk.multiplayer.joinRoom("lobby", { displayName: "Ada" });
const latest = await sdk.multiplayer.getRoomState();
try {
  await sdk.multiplayer.updateRoomState({ ...latest, turn: 2 });
} catch (error) {
  // State conflict: another player wrote first.
  const fresh = await sdk.multiplayer.getRoomState();
  await sdk.multiplayer.updateRoomState({ ...fresh, turn: 2 });
}

Analytics

Nothing to wire. The boot call starts playtime tracking and every SDK call reports player activity. Open the project dashboard to see players, sessions, playtime, and daily activity.

import { sdk, enableAnalyticsByDefault } from "./sdk";

void enableAnalyticsByDefault(); // init + guest + playtime

Prefer it explicit? await sdk.enableAnalytics() does the same thing on demand.

Publishing

Each project gets a publish URL, a game ID, and a publishable key. Build the game to static files, zip the build output with index.html at the root, and upload it — the server injects window.gameId and the client script automatically, so plain static builds just work. Re-upload to redeploy.

cd dist && zip -r ../game.zip . && curl -X POST \
  -H "X-Game-Key: capy_pk_..." \
  -H "Content-Type: application/zip" \
  --data-binary @../game.zip \
  https://game-server.capybara.build/api/games/<game-id>/publish

# → { ok: true, url: https://capybara.build/g/<game-id> }

Need the brief for a coding agent instead? Every project page has a copy-paste integration brief, also available as raw markdown for scripts.