# World HTTP API [Read as Markdown](/raw/world-api.md) · [MCP tools](/mcp-tools/) · [World schema](/world.schema.json) ## Base URL and authentication Use `https://betworld.gg/api/worlds`. All JSON requests use `Content-Type: application/json`. GLB uploads use `Content-Type: model/gltf-binary` and raw file bytes. | Access | How to authenticate | Use for | | --- | --- | --- | | Public | No credential | Browse published worlds and download published documents or assets | | Owner session | Your signed-in Betworld session cookie | Create worlds, manage credentials, inspect your library and publish | | World token | `Authorization: Bearer ` | Let your AI read and edit one world draft and upload assets to your library | Sign in with a registered account for owner routes. In the Betworld website, use `fetch` with `credentials: "same-origin"`; your browser supplies the session cookie and `Origin`. Session-based write requests require `Origin: https://betworld.gg`. For token-based `/ai/...` requests, a command-line or MCP client can omit `Origin`. If supplied, it must match the Betworld origin. Create a token for each world you connect. Tokens remain valid until you revoke or replace them. Store the token in your AI client's secret settings. Owner-session actions, including publishing, are available through your signed-in website session. ## Route index Paths below are relative to `/api/worlds`. | Method | Path | Access | Result | | --- | --- | --- | --- | | GET | `/` | Public | Active world directory, lobby population and limits | | GET | `/mine` | Owner | Your worlds, asset library, usage and earnings | | POST | `/` | Owner | Create a world with a basic starting layout | | GET | `/:id/document` | Public | Published document and published revision | | GET | `/:id/status` | Owner | Draft revision, published revision and entity count | | GET | `/:id/draft` | Owner | Your editable document and asset library | | PUT | `/:id/draft` | Owner | Replace your draft using its current revision | | POST | `/:id/publish` | Owner | Publish a saved revision | | POST | `/:id/ai-token` | Owner | Create or replace the world's AI credential | | DELETE | `/:id/ai-token` | Owner | Revoke the world's AI credential | | POST | `/assets` | Owner | Upload a static GLB to your library | | GET | `/assets/:hash.glb` | Public | Download an immutable converted GLB | | GET | `/ai/draft/document` | World token | Read the connected draft, catalog, limits and schema | | PUT | `/ai/draft/document` | World token | Replace the connected draft | | GET | `/ai/export/world` | World token | Download the draft with asset URLs and Blender references | | POST | `/ai/assets/upload` | World token | Upload a static GLB to the connected owner's library | | POST | `/ai/mcp` | World token | Call the [MCP tools](/mcp-tools/) using JSON-RPC | ## Browse and download ### GET `/` Returns: ```json { "worlds": [ {"id":"public-2","name":"Pyramids","description":"…","ownerId":null,"revision":1,"players":12,"capacity":50} ], "hub": {"id":"lobby","name":"Lobby","players":4}, "limits": {"entities":1000000,"memoryBytes":500000000,"worldBytes":100000000} } ``` The numbers and description above are examples; `limits` contains the full current limit object. Worlds are ordered by descending active player count, then ID. Creator worlds have an `ownerId`; built-in worlds use `null`. `revision` here identifies the published version. ### GET `/:id/document` Returns `{ "id", "name", "document", "revision" }` for a published world. Use this document to inspect the layout players receive. A missing or unpublished world returns `404`. Your newer draft is available through an owner or token draft route. ### GET `/assets/:hash.glb` Replace `:hash` with a returned 64-character lowercase hexadecimal asset hash. The response body is binary `model/gltf-binary`. Asset URLs are immutable and publicly downloadable; keep private material out of assets you upload. Cache responses for repeat visits. Missing hashes return `404`. ## Create and manage your worlds ### POST `/` Request: ```json {"name":"My Desert World","description":"Tables beneath the palms."} ``` `name` is trimmed and must contain 3–48 characters. `description` is optional, trimmed, defaults to an empty string and permits 240 characters. Extra fields are rejected. The JSON request limit is 2 KiB. You can own up to three worlds. Success: `201` with `{ "id": "world-" }`. The new draft starts with the basic layout. Read its draft to get the initial revision before editing. ### GET `/mine` Returns: - `worlds`: your worlds, each with `id`, `name`, `description`, `revision`, `published_revision` and `updated_at`. - `assets`: your library entries, each with `hash`, `name`, `size`, `triangles`, `pixels` and `meshes`. - `usedBytes`: total converted bytes in your asset library. - `earnings.paidCents`: earnings already paid to your balance, in cents. - `earnings.pendingUnits`: the fractional remainder as a decimal string; 1,000,000 units equal one cent. Divide by 1,000,000 to display pending cents. - `limits`: current creator and world budgets. Library entries are ordered by creation time. `meshes` counts rendered primitive parts and contributes to the estimated memory budget. See [publishing and earnings](/publishing/) for the earning formula. ### GET `/:id/status` Returns `{ "id", "name", "revision", "publishedRevision", "entities" }`. Use this lightweight request to refresh the management page after your AI saves a change. A world you do not own returns `404`. ### GET `/:id/draft` Returns `{ "id", "name", "description", "document", "revision", "publishedRevision", "assets" }`. `document` is the complete editable world. Use its `revision` in your next save. ## Save a draft Use **PUT `/:id/draft`** with your owner session, or **PUT `/ai/draft/document`** with the connected world's token. Both accept the same body: ```json { "revision": 1, "document": { "version": 1, "floor": "#dad4c4", "walls": "#e8e1cf", "entities": [ {"id":"blackjack:0","kind":"game","asset":"blackjack","position":[-8,0,4],"yaw":0,"scale":[1,1,1],"color":"#d7ba7f"}, {"id":"bench-east","kind":"prop","asset":"bench","position":[8,0,4],"yaw":3.141592653589793,"scale":[1,1,1],"color":"#42756b"} ] } } ``` This replaces the complete draft, including entities and optional `skins`. Preserve every field and entity you want to keep. The body limit is 64,000,000 bytes, including the revision/document envelope. Follow the [world format](/world-format/) and [game skin contract](/game-skin-contract/) for exact field and placement rules. Success returns: ```json { "revision": 2, "budget": { "entities": 2, "bytes": 25000306, "manifestBytes": 306, "memoryBytes": 138667738, "triangles": 110060, "pixels": 8000000 } } ``` Budget values are illustrative. Use the actual returned values. `revision` increases after every accepted save; submit that new revision next time. Publishing is a separate step. If you receive `409`, download the current draft, merge your changes into it, and retry using its revision. Keep saves sequential. For smaller edits, use MCP `world_edit_entities` to upsert/remove up to 1,000 entities per array. ## Publish ### POST `/:id/publish` Request: `{ "revision": 2 }`, using your latest saved revision. The JSON request limit is 1 KiB. The revision must match the saved draft, the draft must contain a supported game station, all referenced assets must belong to your library, and every world budget must pass. Success returns `{ "ok": true, "pending": true, "revision": 2 }`. The server stores that exact draft as a pending snapshot, blocks new wagers in the world, and waits for existing bets to finish. After at least five seconds of notice and no unsettled wagers, it installs the snapshot. Later draft saves do not change the pending snapshot. A stale revision or a different update already queued returns `409`. Repeating the same queued revision succeeds without replacing or delaying it. Use an authenticated owner session. ### GET `/:id/update` Public, uncached metadata for polling publication progress: ```json { "revision": 1, "pending": true, "pendingRevision": 2, "requestedAt": "2026-11-01T12:00:00.000Z" } ``` `revision` is the currently published revision, or zero for an unpublished world. `pendingRevision` and `requestedAt` become `null` when no publication is pending. While `pending` is true, clients show the update notice and disable new-bet controls while keeping active-game actions enabled. When `revision` increases, download `GET /:id/document` and reload the world renderer. The website does this automatically without replacing the page. ### DELETE `/:id/publish` Authenticated owner session, no body. Cancels a queued update and returns `{ "ok": true }`. The existing published layout and saved draft are retained; new bets reopen. It does not roll back a publication that already completed. ## Create, replace or revoke an AI token ### POST `/:id/ai-token` Send an authenticated owner request with no body. Response: ```json { "token": "<64 lowercase hexadecimal characters>", "endpoint": "https://betworld.gg/api/worlds/ai/mcp", "draftEndpoint": "https://betworld.gg/api/worlds/ai/draft/document", "expiresIn": null } ``` Save the returned token immediately in your client's secret settings. Calling this route again replaces the previous credential for that world. `expiresIn: null` means the credential has no automatic expiry. ### DELETE `/:id/ai-token` Send an authenticated owner request with no body. Returns `{ "ok": true }`. The credential stops working; your saved draft, assets and published world remain available to you. ## Read the connected world with a token ### GET `/ai/draft/document` Returns `worldId`, `revision`, `document`, `assets`, `catalog`, `limits` and `schema`. The token selects your world automatically. `catalog.props` and `catalog.games` contain supported built-ins; `schema` describes the accepted world JSON. ```bash curl --fail-with-body https://betworld.gg/api/worlds/ai/draft/document \ -H "Authorization: Bearer $BETWORLD_TOKEN" ``` ### GET `/ai/export/world` Use this when importing a draft into Blender. It returns the same core draft data plus: - `name`: the world name. - `assets[].url`: an immutable download URL for each asset in your library. - `sources.props`, `sources.games`, `sources.shell`: GLB authoring references. - `gameScale`: the source-to-world scale for the game library. - `authoring`: URLs for model guides, interfaces, the skin contract, socket catalog and skin schema. Preserve the supplied game scale when placing reference models. The [Blender bridge](/blender/) handles coordinate conversion and reference placement for you. ## Upload a prop or game-skin mesh Use **POST `/assets`** with your owner session or **POST `/ai/assets/upload`** with your world token. ```bash curl --fail-with-body https://betworld.gg/api/worlds/ai/assets/upload \ -H "Authorization: Bearer $BETWORLD_TOKEN" \ -H 'Content-Type: model/gltf-binary' \ -H 'X-Model-Name: Palm planter' \ --data-binary @palm-planter.glb ``` Send one static, self-contained GLB of at most **4,000,000 bytes**. The converted result must also fit that limit. `X-Model-Name` is optional; it defaults to `Custom prop` and permits 1–80 characters after trimming. Use embedded textures within the [asset limits](/assets/). Your entire converted asset library can use 100,000,000 bytes. Success: `201` with `{ "hash", "name", "size", "triangles", "pixels", "meshes" }`. `size` is the converted byte count. Place the prop using `asset: "upload:"`, or use the plain hash as a skin's `body` / binding `asset`. Save the draft after uploading to include the placement or skin. Re-uploading identical converted content under the same owner reuses its hash and storage. Revised geometry or materials can produce a new hash; update your document to reference the returned asset. Existing assets continue to occupy library storage. For player characters, use the separate [avatar upload API](/avatar-imports/), whose formats and size limits differ. ## MCP transport Send JSON-RPC requests to **POST `/ai/mcp`** with your world token. Start with `initialize`, honor its returned protocol version, then call `tools/list` or `tools/call`. Use `Accept: application/json, text/event-stream`; parse either response format. The transport is stateless; a persistent GET event stream returns `405`. ```json {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"world_download","arguments":{}}} ``` The [MCP reference](/mcp-tools/) documents all seven tools: `world_download`, `world_upload`, `world_edit_entities`, `game_skin_contract`, `game_skin_save`, `asset_upload` and `asset_download`. Successful tool results contain `structuredContent` and equivalent JSON text. Check `isError` on tool results even when HTTP returns `200`. ## Errors, limits and retrying Application validation failures return `{ "error": "message" }`. Read that message before retrying. Rate-limit and proxy errors may use a text body; check the response Content-Type before parsing JSON. | HTTP status | What to do | | --- | --- | | 400 | Correct the request, model, placement, ownership reference or resource budget described by `error` | | 401 | Sign in to a registered account, or reconnect using your current world token | | 403 | Use the correct origin and authentication method for the route | | 404 | Check the world/asset ID, publication status and world ownership | | 409 | Refresh a stale draft revision, or cancel an existing queued update before publishing a different revision | | 413 | Reduce the request body to the route's byte limit | | 429 | Wait for the rate-limit window indicated by response headers | | 503 | Respect `Retry-After` and retry after the service has capacity | | 500 | Retry a read later; refresh the draft before repeating a save whose outcome is uncertain | World API routes allow 120 requests per minute per client IP. Public GLB downloads have a separate 12,000-per-minute limit. Heavy reads and writes are admitted one at a time; queue your AI's changes instead of sending parallel saves. Proxy limits can also apply. All limits apply together: up to 1,000,000 entities, 100 MB world download, 500 MB estimated runtime memory, 64 MB manifest, 20,000,000 placed triangles, 32,000,000 decoded texture pixels and 50 players per world. Read the live `limits` response and returned `budget` while building. ## In-game editor integration `GET /:id/document` includes `ownerId` alongside `id`, `name`, `document` and `revision`. The site offers Edit this world when it matches the signed-in player. Ownership is checked independently on every draft, asset and publish operation. `GET /:id/draft` also returns `pendingRevision` (an integer or null). The editor uses it to resume displaying a queued publication after reopening. Draft saves continue to use the current `revision`; queued publication uses its own stored snapshot. ## Terrain and panorama routes World documents accept optional `terrain` and `background` settings. Upload a background with `POST /api/worlds/backgrounds` using your owner session, or `POST /api/worlds/ai/backgrounds/upload` using a world Bearer credential. Download canonical WebP bytes from `GET /api/worlds/backgrounds/:hash.webp`. Draft, library and export responses include `backgrounds` metadata alongside the existing GLB `assets`. See [terrain and backgrounds](/terrain-backgrounds/) for every field, limit, header, response and error code.