API reference
The OnlyWorlds API reads and writes world data on onlyworlds.com over HTTPS, in JSON. Two dialects share one host:
/api/v2/: cursor pagination, flat UUID link arrays, create and upsert, bulk writes and a change feed. Use it for new work. Every page in this section describes it unless it says otherwise./api/worldapi/: the Classic API, the original dialect. It stays unchanged and supported for existing clients.
Base URL: https://www.onlyworlds.com/api/v2/. GET /api/ and GET /api/v2/ answer a small JSON index (the interactive reference, the OpenAPI document, the health check and these docs), with no key.
Interactive reference: onlyworlds.com/api/docs · OpenAPI document: onlyworlds.com/api/v2/openapi.json
A first read and write, from minting the key to the reply, is on Getting started.
Authentication
Section titled “Authentication”Every request carries a world key in the API-Key header. Writes also carry the PIN in API-Pin. The key alone decides the world: no world id goes in a path or a body. The one exception is a world its owner opened to everyone: its reads take no key and name the world as ?world=<id> (Public worlds).
curl -s "https://www.onlyworlds.com/api/v2/world" \
-H "API-Key: {key}"Key types, PIN rules, member keys and account tokens are on Keys and PINs.
Which credential each route takes
Section titled “Which credential each route takes”506 operations in 15 route families. Each counts under every credential the API document lists for it. 47 take no key as well as a key or an account token: the keyless reads of a world open to anyone.
/{type}4515544145441GET /character/{id}/contextGET /{type}×22GET /{type}/{id}×22
GET /{type}/{id}/files×22GET /{type}/{id}/files/{file_id}/download×22GET /{type}/{id}/proposals×22POST /{type}/{id}/proposals×22DELETE /{type}/{id}/proposals/{pid}×22
13 routes, 286 operations
POST /{type}×22DELETE /{type}/{id}×22PATCH /{type}/{id}×22PUT /{type}/{id}×22POST /{type}/{id}/adopt×22POST /{type}/{id}/files×22DELETE /{type}/{id}/files/{file_id}×22DELETE /{type}/{id}/hidden×22PUT /{type}/{id}/hidden×22POST /{type}/{id}/links/{field}×22POST /{type}/{id}/proposals/{pid}/accept×22POST /{type}/{id}/proposals/{pid}/decline×22POST /{type}/{id}/review×22
/account000383838 routes, 38 operations
GET /accountPATCH /accountGET /account/allowancePOST /account/askDELETE /account/clients/{client_id}GET /account/invitesPOST /account/invites/{invite_id}/acceptPOST /account/invites/{invite_id}/refusePOST /account/joinPOST /account/sortGET /account/tokensPOST /account/tokensDELETE /account/tokens/{token_id}GET /account/watchedPOST /account/watchedDELETE /account/watched/{watch_id}GET /account/worldsPOST /account/worldsPOST /account/worlds/handoverGET /account/worlds/holding/{narrative_id}POST /account/worlds/{world_id}/invitesDELETE /account/worlds/{world_id}/invites/{invite_id}GET /account/worlds/{world_id}/keysPOST /account/worlds/{world_id}/keysDELETE /account/worlds/{world_id}/keys/{key_id}POST /account/worlds/{world_id}/leaveGET /account/worlds/{world_id}/linksPOST /account/worlds/{world_id}/linksDELETE /account/worlds/{world_id}/links/{link_id}GET /account/worlds/{world_id}/membersDELETE /account/worlds/{world_id}/members/{member_id}PATCH /account/worlds/{world_id}/members/{member_id}POST /account/worlds/{world_id}/messagesPOST /account/worlds/{world_id}/readGET /account/worlds/{world_id}/seatsPOST /account/worlds/{world_id}/seatsDELETE /account/worlds/{world_id}/seats/{seat_id}POST /account/worlds/{world_id}/seats/{seat_id}/key
X-Session-Tokennot no keynot key alonenot key and pinAccount token38/world15626GET /world
GET /world/{world_id}/lenses
GET /world/filesGET /world/hiddenGET /world/proposals
PATCH /world
/links01303GET /links
POST /linksDELETE /links/{link_id}
/me01313GET /me
PUT /me/lenses
PUT /me/character
/auth20002GET /auth/clientsPOST /auth/token
/files20002POST /files/removedPOST /files/uploaded
/health20002GET /healthGET /health/deep
/join20002POST /joinPOST /join/preview
/media00202POST /media/remove-ticketPOST /media/ticket
/bulk00101POST /bulk
/changes01101GET /changes
/members01111GET /members
/public10001GET /public/counts
/search11111GET /search
No key: the document lists no credential.Key alone, as the document declares it: a legacy 10-digit key sends its PIN on every read of a world that has one (Keys and PINs).Account token: 38 of these also take an app's X-Session-Token in its place (Account tokens).
openapi.json): the security each operation lists, as read on 2026-11-01. Paths are under /api/v2/; {type} is any of the 22 element types.Response shapes
Section titled “Response shapes”A list answers in an envelope:
{ "data": [], "has_more": false, "next_cursor": null }A single element answers as the bare element object. Every error answers in one envelope, {"error": {"type", "code", "message", "param", "doc_url"}}, described on the error reference.
Resources
Section titled “Resources”{type} is one of the 22 element types, by its name in lowercase: character, location, and so on (the OpenAPI document enumerates them). The URL names the element type; the key names the world.
| Method | Route | Purpose | Page |
|---|---|---|---|
| GET | /api/v2/{type} |
List elements (paginated, filterable) | Reads |
| GET | /api/v2/{type}/{id} |
Get one element | Reads |
| POST | /api/v2/{type} |
Create | Writes |
| PUT | /api/v2/{type}/{id} |
Create or replace by id (upsert) | Writes |
| PATCH | /api/v2/{type}/{id} |
Partial update | Writes |
| DELETE | /api/v2/{type}/{id} |
Delete | Writes |
| POST | /api/v2/{type}/{id}/links/{field} |
Add or remove ids on one multi-link field | Link fields |
| POST | /api/v2/bulk |
Create and upsert many elements of mixed element types | Writes |
| GET | /api/v2/search |
Find elements by the words in their name and text | Reads |
| GET | /api/v2/character/{id}/context |
One Character’s context in one read | The draught |
| GET | /api/v2/changes |
The world’s change feed, and full export | Changes |
| GET | /api/v2/world |
The world named by the key | Reads |
| PATCH | /api/v2/world |
Update the world’s own fields (the owner, or a co-builder for every writable field but owner_character) |
Writes |
| POST | /api/v2/{type}/{id}/adopt |
Change who made an element (the owner and co-builders) | Members |
| GET | /api/v2/members |
The world’s roster | Members |
| GET | /api/v2/me |
Who the calling key is | Members |
| PUT | /api/v2/me/character |
A person member picks the Character that is them | Members |
| PUT | /api/v2/me/lenses |
Choose the lenses you start with | Lenses on the API |
| GET | /api/v2/world/{world_id}/lenses |
The world’s lenses, resolved for you | Lenses on the API |
| GET, POST | /api/v2/{type}/{id}/proposals |
An element’s proposals; propose a change | Proposed changes |
| POST, DELETE | /api/v2/{type}/{id}/proposals/{pid} (/accept, /decline) |
Accept, decline or withdraw a proposal | Proposed changes |
| GET | /api/v2/world/proposals |
The world’s proposals (the queue) | Proposed changes |
| POST | /api/v2/{type}/{id}/review |
Accept or decline a proposed new element | Proposed changes |
| GET, POST, DELETE | /api/v2/links, /api/v2/links/{link_id} |
A co-builder’s invite links | Members |
| POST | /api/v2/media/ticket |
A ticket for one image upload | Images |
| POST | /api/v2/media/remove-ticket |
A ticket to remove one uploaded image | Images |
| GET, POST | /api/v2/{type}/{id}/files |
An element’s files; a ticket for one file upload | Files |
| GET | /api/v2/{type}/{id}/files/{file_id}/download |
A download link for one file | Files |
| DELETE | /api/v2/{type}/{id}/files/{file_id} |
Remove a file | Files |
| GET | /api/v2/world/files |
Every file in the world the key may see, and the world’s total | Files |
| PUT, DELETE | /api/v2/{type}/{id}/hidden |
Hide or unhide an element (the owner and co-builders) | Hidden elements |
| GET | /api/v2/world/hidden |
The world’s hidden elements (the owner and co-builders) | Hidden elements |
| POST | /api/v2/join/preview |
Read a join link (agent, person or player) without using it | Agents |
| POST | /api/v2/join |
Redeem an agent’s or a player’s code, no key | Agents, Members |
| GET | /api/v2/auth/clients |
The tools registered to sign a person in, no key | Sign in with OnlyWorlds |
| POST | /api/v2/auth/token |
Trade a sign-in code for an account token, no key | Sign in with OnlyWorlds |
| GET | /api/v2/public/counts |
The platform’s totals, no key | Public worlds |
| GET | /api/v2/health |
Liveness check, no key needed | |
| GET, PATCH | /api/v2/account |
The account itself, on an account token | Sign in with OnlyWorlds |
| various | /api/v2/account/... |
Account token routes: worlds, keys, invites, members, links, seats, watched worlds | Keys and PINs, Members |
Trailing slashes are tolerated on every route: /api/v2/character/{id} and /api/v2/character/{id}/ both resolve, with no redirect.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Request rate | 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Reads and writes count together, and a /bulk call is one request. The minute is the clock’s. Every counted reply carries RateLimit-Limit (the budget), RateLimit-Remaining (what is left this minute) and RateLimit-Reset (seconds to the minute’s end), errors included. Over the budget: 429 too_many_requests with Retry-After. The health check, the Classic API and the MCP server are not counted |
| Busy server | Under load the server answers 503 server_busy with a Retry-After header: each worker process runs six requests at once, and a request waits up to 10 seconds for a slot |
| Request body | Up to 2.5 MB (2,621,440 bytes). A larger body answers 413 payload_too_large and nothing is written. Split a large /bulk into several calls |
| Wrong PINs | Repeated failures lock the key out: 429 rate_limited with Retry-After |
/bulk |
Up to 1000 items per request |
| List pages | limit from 1 to 1000, default 100; a larger one is read as 1000. Search takes 1 to 200, default 50, the same way. The change feed defaults to 500 and refuses more than 1000 (422) |
| Images | 15 MB an image; 200 upload tickets a world in 24 hours (429 quota_exceeded); 1 GB of images per account by default (403 storage_full). See Images |
| Files | 25 MB a file, 250 MB a world, 200 file tickets a world in 24 hours. See Files |
expand |
One level deep: a stub is {id, name, supertype, subtype, image_url}, with no links of its own |
Sizes count on a 1024 base: 1 MB is 1,048,576 bytes and 1 GB is 1,073,741,824 bytes. The API document lists the API’s limits, each with its exact value, as info.x-ow-limits in openapi.json.
There are no webhooks. To follow a world’s changes, poll /changes with your stored cursor; ?head=true is the cheap check for whether anything moved.
Stability
Section titled “Stability”/api/v2/is the current API. The Classic API at/api/worldapi/keeps answering for the tools built on it; it is frozen, not developed.- New fields and routes arrive without notice and never change what existing fields mean. A field, route or behaviour is removed only after it has been announced on the feedback page’s changelog and in the API document, at least six months ahead.
- Every reply’s
X-OW-Schema-Versionheader names the version of the standard that shapes it. - Error codes and field names are a contract: a client may branch on them. Error messages are not.
- The standard’s files, the TypeScript SDK, the Unity SDK, the toolkit and schema-dist are MIT-licensed.
Watching the API document. A tool that watches the document reads it like this: ask for gzip (it is about 3 MB plain, about 300 KB compressed), keep the ETag the answer carries, and send it back on the next read in If-None-Match exactly as received (it may arrive weak, W/"…"; keel accepts either). An unchanged document answers 304 with no body; it also serves Cache-Control: public, max-age=300, so nothing is gained by reading it more often than every five minutes.
API pages
Section titled “API pages”| Page | Covers |
|---|---|
| Keys and PINs | Key types, the PIN, member and agent-seat keys, account tokens |
| Reads and pagination | The list envelope, cursors, filters, search, expansion, sparse fields, the world |
| Link fields | How links read and write, and the link operations route |
| Writes and bulk | Create, upsert, patch, delete, idempotency, bulk |
| Changes and export | The change feed, full export, guests’ cursors |
| Members and sharing | Roles, the roster, guests, invites, player links, agent seats, read keys |
| Hidden elements | Hiding an element from everyone but the owner, the co-builders and its creator |
| The draught | One Character’s context in one read, under a policy |
| Lenses on the API | A world’s lenses and their readings, and the lenses a member starts with |
| Proposed changes | Proposing a change to an element, the queue, accepting and declining |
| Public worlds | A world open to everyone: its page and the keyless reads |
| Sign in with OnlyWorlds | How a registered tool signs a person in through onlyworlds.com and gets an account token |
| Images | Hosting an element’s picture on OnlyWorlds |
| Files | Attaching PDFs, text files and world bundles to an element |
| CORS | Which browser origins may call the API |
| Classic API | The original /api/worldapi/ dialect |
| Error reference | Every error code, its cause and its fix |
