Reference

API Reference

Everything the CLI, SDK, and portal UI do goes through these HTTP APIs. Authenticated endpoints accept Authorization: Bearer mtl_api_… (scoped) or mtl_cli_… tokens — see API Tokens. Three origins are involved: the portal (https://www.metaloot.app), the Studio (https://studio.metaloot.app), and your game's own domain (<slug>.metaloot.app).

OAuth (Portal)#

The identity provider behind Sign in with Metaloot — full protocol walkthrough in Authentication.

GET /oauth/authorizeAuthorization endpoint. Params: response_type=code, client_id, redirect_uri, scope, state.
POST /api/oauth/tokenExchange an authorization code (form-encoded: grant_type, client_id, client_secret, code, redirect_uri). Server-side only.
GET /api/oauth/userinfoThe player profile for a bearer access token; sub is the stable player id.

Portal API#

GET /api/cli/whoamiIdentify the bearer token → { user: { id, name, email? }, tokenLabel }. The cheapest way to validate a token.
DELETE /api/cli/tokensRevoke the calling token (what metaloot logout uses).
GET | POST /api/tokens · DELETE /api/tokens/{id}Manage scoped API tokens (browser session required) — see API Tokens.
POST /api/gamesCreate a game: { title, description, url?, tags[], visibility? }, where visibility is public or private. Scope: games:write.
PATCH /api/games/{id}Update title, description, url (re-syncs the auth redirect URI), playable, or visibility. Scope: games:write.
POST /api/oauth/appsIssue OAuth credentials for a game: { name, homepageUrl, gameId, redirectUris } → client ID plus one-time secret. Scope: oauth:write.
POST /api/deployStart a deploy: returns { deployId, uploads: [{ path, uploadUrl }] } presigned URLs. Scope: deployments:write.
POST /api/deploy/{deployId}/finalizeAtomically flip the site live: returns { url, playUrl, manageUrl, auth, multiplayer }. Scope: deployments:write.
curl https://www.metaloot.app/api/cli/whoami -H "Authorization: Bearer mtl_api_…"

AI API (Portal)#

Credit-billed generation for games — the walkthrough lives in AI Generation. Both routes take Authorization: Bearer mtl_api_… with the ai:generate scope, send Access-Control-Allow-Origin: *, and answer OPTIONS, so browser games can call them from any origin.

POST /api/ai/generateBody: { type: "text" | "object" | "image", prompt, system?, schema?, model?, gameId?, maxOutputTokens?, temperature?, aspectRatio?, imageSize? }. prompt is required and capped at 8000 characters; schema (JSON Schema) is only valid for type: "object"; maxOutputTokens defaults to 4096 and caps at 16384; aspectRatio / imageSize ("1K" | "2K") apply to images only.
→ 200{ id, type, model, result, creditsUsed, balance }. result is the assistant string for text, the parsed object for object, and { b64Json, contentType } for image — base64 always, since upstream image URLs are not publicly fetchable.
→ errors{ error: { message, code } } 400 invalid_request, 402 insufficient_credits (with balance and required), 502 upstream_error, 503 ai_not_configured. Nothing is charged unless generation succeeded.
GET /api/ai/credits{ balance, ledger: [{ amount, kind, description, createdAt }] } — the last 20 ledger entries. First call also grants the 100 free signup credits.
POST /api/stripe/checkoutBrowser-session only. Body: { packId } { url } to redirect to. Returns 503 billing_not_configured until live Stripe keys are set.
POST /api/stripe/webhookStripe-signed only (stripe-signature). Grants the purchased credits on checkout.session.completed, idempotent on the event id.
curl -X POST https://www.metaloot.app/api/ai/generate \
  -H "Authorization: Bearer mtl_api_…" \
  -H "Content-Type: application/json" \
  -d '{"type":"text","prompt":"Name three swamp-level hazards."}'

Studio Assets API#

Public asset endpoints send Access-Control-Allow-Origin: * (GET/HEAD/OPTIONS), so games can hot-link them from any origin. Private assets return the same headers only to their owner (bearer token with assets:read) and 404 to everyone else.

GET /api/assets/{id-or-slug}/fileThe hosted file. Query: variant=auto|source|lod (default auto; response carries X-Metaloot-Variant), and path=<file> for one file out of a pack (omit for the pack ZIP).
GET /api/assets/{id}/animation/{preset}A rigged animation preset as its own GLB (rigged model + that clip).
Asset metadata & listingUse the SDK's getAsset(), listAssets(), and getAssetManifest(), or the CLI with --json — the underlying routes are versioned with the SDK.

Cache behavior:

public assetsCache-Control: public, max-age=86400
private assetsCache-Control: private, no-store
curated pack filesCache-Control: public, max-age=31536000, immutable

Blender Worker API (via Studio)#

Local agents should normally use metaloot blender. The CLI calls these authenticated Studio routes; clients never receive the private worker origin or service token. Read operations require assets:read, job submission requires assets:generate, and cancellation requires assets:write.

GET /api/blender/healthLive executor type, worker capacity, active/queued counts, and starter-library health.
GET /api/blender/v1/capabilitiesSupported job actions, request inputs, output artifacts, script-size limit, queue limit, and timeout.
GET /api/blender/v1/templatesVersioned Blender starting scenes, parameter schemas, recommended starter ids, and agent guidance.
GET /api/blender/v1/librarySemantic starter catalog with kinds, data-block names, provenance, licenses, checksums, quality, and coordinate metadata.
GET | POST /api/blender/v1/jobsList owner-scoped jobs or submit a headless Blender job with { name, action?, template?, parameters?, starterAssets?, script?, sourceJobId?, faceLimit? }.
GET /api/blender/v1/jobs/{id}Job status, progress, queue position, action, timing, and terminal error.
POST /api/blender/v1/jobs/{id}/cancelCancel an owner-scoped queued or running job.
POST /api/blender/v1/jobs/{id}/actionsQueue revise, render, inspect, or export work against a completed job, optionally with another bpy script.
GET /api/blender/v1/jobs/{id}/artifacts[/{name}]List or download model.glb, preview.png, inspection.json, and scene.blend.
POST /api/generationsHigh-level Studio asset creation. The metaloot blender create command sends provider: "blender" plus the selected template, starters, parameters, and script; Studio tracks the job and persists successful outputs as a normal asset.
The caller supplies the Blender Python. These routes execute jobs; they do not host a remote AI agent or translate arbitrary prompts into Blender operations.

Game Domain Routes#

Provisioned on every deployed game at https://<slug>.metaloot.app, alongside your static files:

GET /auth/metaloot/startBegin sign-in (redirects to the portal).
GET /auth/metaloot/callbackOAuth callback — sets the signed session cookie.
GET /auth/metaloot/sessionCurrent session: { signedIn, user, scope, expiresAt } or { signedIn: false }.
GET | POST /auth/metaloot/logoutClear the game session.
GET /mpMultiplayer availability: { enabled, version, rooms, client, docs }.
wss://…/mp/rooms/{roomId}Room WebSocket. Authenticated by the same-origin session cookie; unauthenticated upgrades get 401 { error: "auth_required", signIn: "/auth/metaloot/start" }. Room ids match [A-Za-z0-9_.~-]{1,64}.
GET /__metaloot/multiplayer.jsZero-install multiplayer client (ES module).
GET /__metaloot/auth-widget.jsThe injected sign-in widget.
GET /__metaloot/assets/{id-or-slug}.glbSame-origin, edge-cached proxy to the Studio file endpoint. Public assets only; Access-Control-Allow-Origin: *, max-age=86400.
These routes are reserved — a deploy cannot ship files under /auth/metaloot/, /mp/, or /__metaloot/. Everything else falls through to your static files with SPA fallback to index.html.