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[] }. Scope: games:write.
PATCH /api/games/{id}Update title, description, url (re-syncs the auth redirect URI), or playable. 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_…"

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

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.