Skip to content

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.

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).

Terminal window
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.

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.

RoutesNo keyKey aloneKey and PINAccount tokenAll
/{type}4515544145441
  • GET /character/{id}/context
  • GET /{type}×22
  • GET /{type}/{id}×22
No keyKey aloneKey and PINAccount token45
  • GET /{type}/{id}/files×22
  • GET /{type}/{id}/files/{file_id}/download×22
  • GET /{type}/{id}/proposals×22
  • POST /{type}/{id}/proposals×22
  • DELETE /{type}/{id}/proposals/{pid}×22
not no keyKey aloneKey and PINnot account token110
13 routes, 286 operations
  • POST /{type}×22
  • DELETE /{type}/{id}×22
  • PATCH /{type}/{id}×22
  • PUT /{type}/{id}×22
  • POST /{type}/{id}/adopt×22
  • POST /{type}/{id}/files×22
  • DELETE /{type}/{id}/files/{file_id}×22
  • DELETE /{type}/{id}/hidden×22
  • PUT /{type}/{id}/hidden×22
  • POST /{type}/{id}/links/{field}×22
  • POST /{type}/{id}/proposals/{pid}/accept×22
  • POST /{type}/{id}/proposals/{pid}/decline×22
  • POST /{type}/{id}/review×22
not no keynot key aloneKey and PINnot account token286
/account0003838
38 routes, 38 operations
  • GET /account
  • PATCH /account
  • GET /account/allowance
  • POST /account/ask
  • DELETE /account/clients/{client_id}
  • GET /account/invites
  • POST /account/invites/{invite_id}/accept
  • POST /account/invites/{invite_id}/refuse
  • POST /account/join
  • POST /account/sort
  • GET /account/tokens
  • POST /account/tokens
  • DELETE /account/tokens/{token_id}
  • GET /account/watched
  • POST /account/watched
  • DELETE /account/watched/{watch_id}
  • GET /account/worlds
  • POST /account/worlds
  • POST /account/worlds/handover
  • GET /account/worlds/holding/{narrative_id}
  • POST /account/worlds/{world_id}/invites
  • DELETE /account/worlds/{world_id}/invites/{invite_id}
  • GET /account/worlds/{world_id}/keys
  • POST /account/worlds/{world_id}/keys
  • DELETE /account/worlds/{world_id}/keys/{key_id}
  • POST /account/worlds/{world_id}/leave
  • GET /account/worlds/{world_id}/links
  • POST /account/worlds/{world_id}/links
  • DELETE /account/worlds/{world_id}/links/{link_id}
  • GET /account/worlds/{world_id}/members
  • DELETE /account/worlds/{world_id}/members/{member_id}
  • PATCH /account/worlds/{world_id}/members/{member_id}
  • POST /account/worlds/{world_id}/messages
  • POST /account/worlds/{world_id}/read
  • GET /account/worlds/{world_id}/seats
  • POST /account/worlds/{world_id}/seats
  • DELETE /account/worlds/{world_id}/seats/{seat_id}
  • POST /account/worlds/{world_id}/seats/{seat_id}/key
or X-Session-Token
not no keynot key alonenot key and pinAccount token38
/world15626
  • GET /world
No keyKey aloneKey and PINAccount token1
  • GET /world/{world_id}/lenses
not no keyKey aloneKey and PINAccount token1
  • GET /world/files
  • GET /world/hidden
  • GET /world/proposals
not no keyKey aloneKey and PINnot account token3
  • PATCH /world
not no keynot key aloneKey and PINnot account token1
/links01303
  • GET /links
not no keyKey aloneKey and PINnot account token1
  • POST /links
  • DELETE /links/{link_id}
not no keynot key aloneKey and PINnot account token2
/me01313
  • GET /me
not no keyKey aloneKey and PINnot account token1
  • PUT /me/lenses
not no keynot key aloneKey and PINAccount token1
  • PUT /me/character
not no keynot key aloneKey and PINnot account token1
/auth20002
  • GET /auth/clients
  • POST /auth/token
No keynot key alonenot key and pinnot account token2
/files20002
  • POST /files/removed
  • POST /files/uploaded
No keynot key alonenot key and pinnot account token2
/health20002
  • GET /health
  • GET /health/deep
No keynot key alonenot key and pinnot account token2
/join20002
  • POST /join
  • POST /join/preview
No keynot key alonenot key and pinnot account token2
/media00202
  • POST /media/remove-ticket
  • POST /media/ticket
not no keynot key aloneKey and PINnot account token2
/bulk00101
  • POST /bulk
not no keynot key aloneKey and PINnot account token1
/changes01101
  • GET /changes
not no keyKey aloneKey and PINnot account token1
/members01111
  • GET /members
not no keyKey aloneKey and PINAccount token1
/public10001
  • GET /public/counts
No keynot key alonenot key and pinnot account token1
/search11111
  • GET /search
No keyKey aloneKey and PINAccount token1
All5616545988506

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).

Generated from the API's own document (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.

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.

{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.

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.

  • /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-Version header 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.

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