This is the full developer documentation for OnlyWorlds
# OnlyWorlds Docs
> An open schema for world data, and the API, SDKs and agent interfaces to build on it.
[Build an app](/docs/development/packages)The TypeScript, Python and Unity SDKs, or plain REST.
[Connect an agent](/docs/development/ai)The MCP server, agent seats, the LLM guide and the toolkit.
[Understand the schema](/docs/schema/)The 22 element categories, their fields and typed links.
[Build a world without code](https://www.onlyworlds.com/start)Start at onlyworlds.com/start, or open Atlas: a world as plain files on your own disk, no account needed.
## Quickstart
[Section titled “Quickstart”](#quickstart)
List the Characters in Hyperion, the public example world. Its demo key `0000000000` is read-only and reads without a PIN; Moppetopia’s demo key is listed on [Keys and PINs](/docs/getting-started/keys#demo-keys).
```bash
curl -H "API-Key: 0000000000" \
"https://www.onlyworlds.com/api/v2/character/?fields=id,name"
```
```json
{
"data": [
{ "id": "068e92dc-10ec-7225-8000-7efd678bd862", "name": "Mr. Steinway" },
{ "id": "068e92db-4c40-79eb-8000-8ef44c0d52b4", "name": "Rachmaninov" },
{ "id": "068e2ddb-83c6-71a7-8000-a45b8b711e25", "name": "The Consul" }
],
"has_more": false,
"next_cursor": null
}
```
Swap in a key for your own world and the same call reads it. [Getting Started](/docs/getting-started/) walks through keys and the first write.
Looking for tools to build a world with, rather than to build on? Start at [onlyworlds.com/start](https://www.onlyworlds.com/start), or open [Atlas](https://atlas.onlyworlds.com).
# API Error Reference
> Every error code the OnlyWorlds API returns, what causes it, and how to fix it.
Every error from the current API (`/api/v2/`, including `/bulk` and `/changes`) answers in one envelope. A response with a non-2xx status has this body:
```json
{
"error": {
"type": "invalid_request",
"code": "invalid_link",
"message": "friends references Character '0123…' which does not exist.",
"param": "friends",
"doc_url": "https://onlyworlds.github.io/api/errors#invalid_link"
}
}
```
| Field | Meaning |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | The error family, one of a fixed set: `invalid_request`, `authentication_error`, `permission_error`, `not_found`, `rate_limited`, `idempotency_error`, `api_error`. It names the family only: one family holds codes with different statuses and different fixes |
| `code` | The specific, stable identifier. Decide what to do from this, never from `type` |
| `message` | Human-readable and safe to log or show. Its wording may change: do not parse it |
| `param` | The field or query parameter at fault, when there is one; otherwise `null` |
| `doc_url` | This page, at the code’s section (`#`) |
Each code has its own section below, and the summary table lists them all. A fetch of this page gets every section; each code’s section has the id ``, so `doc_url`’s `#` lands on it. The same page as Markdown is at [`/api/errors.md`](/api/errors.md), where each section is the heading `### `. In `/bulk`, each failed item carries this same envelope: see [Bulk Errors](#bulk-errors). MCP tools report failures as tool errors carrying the same human message, without the envelope fields.
Note
The [Classic API](/docs/development/api/classic) at `/api/worldapi/` answers in its own legacy shape, `{"detail": …}`. The codes on this page apply to `/api/v2/` only.
## Error Codes
[Section titled “Error Codes”](#error-codes)
| Code | Type | Status | What happened |
| --------------------------------------------- | ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| [`invalid_request`](#invalid_request) | `invalid_request` | `422` | The body or query is malformed: an unknown field or query parameter, a bad value, or a wrong-shaped payload; `param` names the culprit. |
| [`invalid_link`](#invalid_link) | `invalid_request` | `400` | A link field names an element that does not exist in this world (or among a /bulk batch’s surviving items). |
| [`id_conflict`](#id_conflict) | `invalid_request` | `409` | A create supplied an id that already exists, in this world or another (element ids are unique across all worlds). |
| [`resync_required`](#resync_required) | `invalid_request` | `409` | A guest’s /changes cursor predates a change to what it can see, or is not this caller’s cursor shape: pull again from since=0. |
| [`already_member`](#already_member) | `invalid_request` | `409` | The invite names someone who is already a member, or the accepting account already is one. |
| [`pin_required`](#pin_required) | `invalid_request` | `409` | A member without an account PIN tried to accept an invite or mint a write key. |
| [`apply_failed`](#apply_failed) | `invalid_request` | `422` | A /bulk item passed validation but its write failed a database constraint (most often an id that already exists in another world). |
| [`invalid_credentials`](#invalid_credentials) | `authentication_error` | `401` | The API key or PIN is missing, unrecognised or wrong. |
| [`key_revoked`](#key_revoked) | `authentication_error` | `401` | The API key was recognised but has been revoked. |
| [`permission_error`](#permission_error) | `permission_error` | `403` | The credential is valid but lacks the scope for this route (e.g. a read key on a write route). |
| [`not_author`](#not_author) | `permission_error` | `403` | A contributor or guest key changed, replaced, relinked or deleted an element someone else created. |
| [`owner_only`](#owner_only) | `permission_error` | `403` | A member key, even a co-builder’s, tried to change the world’s own settings. |
| [`guest_not_supported`](#guest_not_supported) | `permission_error` | `403` | A guest key called a route guests cannot use yet. |
| [`storage_full`](#storage_full) | `permission_error` | `403` | The account the upload counts against has used all of its image storage. |
| [`not_found`](#not_found) | `not_found` | `404` | The element or route does not exist in this world. |
| [`rate_limited`](#rate_limited) | `rate_limited` | `429` | Too many failed PIN attempts; retry after the Retry-After header’s seconds. |
| [`quota_exceeded`](#quota_exceeded) | `rate_limited` | `429` | The world has used its image upload tickets for the day; retry after Retry-After. |
| [`idempotency_error`](#idempotency_error) | `idempotency_error` | `409` | An Idempotency-Key was reused with a different request body. |
| [`api_error`](#api_error) | `api_error` | `500` | An unexpected server-side error; the envelope is kept even here. |
| [`server_busy`](#server_busy) | `api_error` | `503` | Every request slot stayed full for 10 seconds; retry after Retry-After. |
| [`payload_too_large`](#payload_too_large) | `api_error` | `413` | The request body is over 8 MB. |
| [`media_unavailable`](#media_unavailable) | `api_error` | `503` | Image upload is not configured on the server right now. |
### invalid_request
[Section titled “invalid_request”](#invalid_request)
**Type** `invalid_request` · **Status** `422`
The request body or query is malformed: an unknown field, a server-managed field in a write, an unknown query parameter, a value that fails validation, text over its length limit, extension fields over the size cap, a malformed id, or a wrong-shaped payload.
* **Common cause:** a misspelled field name (`freinds`); a `_ids` or `_id` suffix carried over from the [Classic API](/docs/development/api/classic) (the current API uses bare link names); an unknown query parameter, such as a mistyped filter (`nmae__icontains`) or `?ordering=`; sending a read body back with `type`, `created_at`, `updated_at` or `change_seq` still in it; a bulk request whose `items` is not an array.
* **How to fix:** read `param`: it names the field or parameter at fault. Correct the spelling, drop the suffix or the server field, or remove the parameter. The list filters are `name`, `name__icontains`, `supertype` and `subtype`, plus `characters` on the six categories with that link; the other accepted parameters are `limit`, `cursor`, `expand` and `fields`. Any other query parameter is rejected, never ignored.
Unknown fields are a hard error, not a silent drop, so a mistyped field name fails loudly instead of vanishing. Fields under the extension namespaces `atlas_*`, `shadow_*` and `x_*` are the exception: they are stored as written.
### invalid_link
[Section titled “invalid_link”](#invalid_link)
**Type** `invalid_request` · **Status** `400`
A link field names a UUID that is not an element of the linked category in this world.
* **Common cause:** linking to an element that was never created, was deleted, or whose UUID was mistyped. An id of an element of another category (a Location’s id in `friends`, which holds Characters). In `/bulk`, linking to a sibling item that itself failed. For a guest key, linking to an element the guest cannot see.
* **How to fix:** check that the referenced element exists and is of the category the field names (read it first, or include it in the same `/bulk` batch). Links in a batch are checked against the world **plus** the batch’s surviving items, in any order, but an item that fails cannot be linked to. `param` names the link field.
A single write that fails with `invalid_link` writes nothing, so after fixing the link you can retry it with the same element id. A dangling link is an explicit, named error. The Classic API’s old behaviour of silently dropping it does not apply here.
### id_conflict
[Section titled “id_conflict”](#id_conflict)
**Type** `invalid_request` · **Status** `409`
A `POST` supplied an `id` that already exists, or a `PUT` named an id held by an element in another world. Element ids are unique across **all** worlds, so the collision may be with another world. The message says which case it is.
* **Common cause:** retrying a create with a client-minted UUID that already landed, or reusing ids copied from another world.
* **How to fix:** if the id exists in this world, use `PUT /api/v2/{type}/{id}`, the upsert: `POST` only creates. If it exists in another world, mint a new id. To retry a request that may have completed, send an `Idempotency-Key` header instead of posting again.
### resync_required
[Section titled “resync_required”](#resync_required)
**Type** `invalid_request` · **Status** `409`
A guest’s `/changes` cursor is from before a change to what the guest can see, or the cursor is not this caller’s shape (a guest’s has three parts, everyone else’s two).
* **How to fix:** pull the feed again from `since=0` and replace the local copy. See [Guests’ Cursors](/docs/development/api/changes#guests-cursors).
### already_member
[Section titled “already_member”](#already_member)
**Type** `invalid_request` · **Status** `409`
An invite names someone who is already a member of the world, or an invite is accepted by an account that is already a member.
* **How to fix:** nothing to do; the membership exists. Check the roster with `GET /api/v2/members`.
### pin_required
[Section titled “pin_required”](#pin_required)
**Type** `invalid_request` · **Status** `409`
A member without an account PIN tried to accept an invite or mint a write key. Member keys write with the member’s own account PIN.
* **How to fix:** set a PIN in your [account settings](https://www.onlyworlds.com/account/settings), then retry.
### invalid_credentials
[Section titled “invalid_credentials”](#invalid_credentials)
**Type** `authentication_error` · **Status** `401`
The API key or PIN is missing, unknown or wrong.
* **Common cause:** no `API-Key` header, a mistyped key, or a missing or wrong `API-Pin` on a write. Only a legacy 10-digit key also needs the PIN to read a private world; prefixed keys (`ow_w_`, `ow_r_`) read without it. A deleted world’s keys are deleted with it, so they also answer `invalid_credentials`.
* **How to fix:** check the `API-Key` and `API-Pin` headers (exact names) and that the key belongs to the world you mean. The `message` tells the two apart: `No valid API-Key.` for the key, `Incorrect PIN.` for the PIN. A key that reads with `200` but writes with `401` has a wrong PIN, since reads with a prefixed key never check it. Keys are minted in the [account portal](https://www.onlyworlds.com/account/). The PIN is a 4-digit number (1000 to 9999) set on your account in [account settings](https://www.onlyworlds.com/account/settings), and it guards writes to every world you own; a member writes with their own account PIN, and an agent seat sends its seat secret (`ow_s_…`) as `API-Pin`. See [Keys and PINs](/docs/getting-started/keys#checking-the-pin).
### key_revoked
[Section titled “key_revoked”](#key_revoked)
**Type** `authentication_error` · **Status** `401`
The API key was recognized but has been revoked.
* **Common cause:** a key that was revoked in the account portal is still used by an old client or script.
* **How to fix:** mint a new key in the [account portal](https://www.onlyworlds.com/account/) and update the client. Revocation is permanent for that key.
### permission_error
[Section titled “permission_error”](#permission_error)
**Type** `permission_error` · **Status** `403`
The credential is valid but lacks the scope for this route.
* **Common cause:** a read key (`ow_r_…`) on a write route: create, update, delete, link operations, bulk, or an image ticket.
* **How to fix:** use a write key (`ow_w_…`, or a legacy key) for writes. A `403` means the key is genuine and cannot do this; a `401` [`invalid_credentials`](#invalid_credentials) means the key itself is not accepted.
### not_author
[Section titled “not_author”](#not_author)
**Type** `permission_error` · **Status** `403`
A contributor or guest key changed, replaced, relinked or deleted an element someone else created. In `/bulk` it is reported per item.
* **How to fix:** these roles change only their own elements (`created_by` names the creator). Ask the owner for the co-builder role, or leave the element alone. See [Roles](/docs/development/api/members#roles).
### owner_only
[Section titled “owner_only”](#owner_only)
**Type** `permission_error` · **Status** `403`
A member key, even a co-builder’s, tried to change the world’s own fields (`PATCH /api/v2/world`).
* **How to fix:** only the owner’s key can do this.
### guest_not_supported
[Section titled “guest_not_supported”](#guest_not_supported)
**Type** `permission_error` · **Status** `403`
A guest key called a route guests cannot use (world sharing, token status).
* **How to fix:** use a key with a role other than guest, or skip the route.
### storage_full
[Section titled “storage_full”](#storage_full)
**Type** `permission_error` · **Status** `403`
`POST /api/v2/media/ticket`: the account the upload counts against has used all of its image storage. The message names the cap.
* **How to fix:** no ticket is issued. Use an image hosted elsewhere (`image_url` takes any URL), or contact about the cap.
### not_found
[Section titled “not_found”](#not_found)
**Type** `not_found` · **Status** `404`
The requested element or route does not exist.
* **Common cause:** a read, `PATCH` or link operation on an element UUID that is not in this world, or a mistyped path. For a guest key, an element the guest cannot see. An agent join code that is unknown, used, revoked or expired also answers `not_found`, the same answer for every case.
* **How to fix:** check that the UUID exists in this world and that the path is right. `DELETE` is idempotent: deleting an element that is already gone returns `204`, not `404`.
### rate_limited
[Section titled “rate_limited”](#rate_limited)
**Type** `rate_limited` · **Status** `429`
Too many failed authentication attempts, such as repeated wrong PINs.
* **Common cause:** a script retrying with a wrong PIN in a tight loop.
* **How to fix:** wait the number of seconds in the `Retry-After` header. Repeated failures escalate to a temporary lockout, so fix the credential before retrying.
### quota_exceeded
[Section titled “quota_exceeded”](#quota_exceeded)
**Type** `rate_limited` · **Status** `429`
`POST /api/v2/media/ticket`: the world has used its 200 image upload tickets for the day.
* **How to fix:** retry after the `Retry-After` header’s seconds.
### idempotency_error
[Section titled “idempotency_error”](#idempotency_error)
**Type** `idempotency_error` · **Status** `409`
An `Idempotency-Key` header was reused with a **different** request body.
* **Common cause:** reusing one idempotency key across two different requests.
* **How to fix:** one key names exactly one request: use a new key (a new UUID) for each distinct write. Replaying the identical body with the same key is fine: it returns the original stored response, with an `Idempotent-Replay: true` header, and does not write twice. See [Idempotency](/docs/development/api/writes#idempotency).
### api_error
[Section titled “api_error”](#api_error)
**Type** `api_error` · **Status** `500`
An unexpected server-side error. The envelope holds even here: the API never answers with a bare HTML error page.
* **Common cause:** a bug or a passing infrastructure fault on the server, not your request’s shape.
* **How to fix:** retry after a short delay. If it persists, report it to with the time and the request. Server errors are logged on our side.
### server_busy
[Section titled “server_busy”](#server_busy)
**Type** `api_error` · **Status** `503`
Every request slot on the server stayed full for 10 seconds.
* **How to fix:** retry after the `Retry-After` header’s seconds.
### payload_too_large
[Section titled “payload_too_large”](#payload_too_large)
**Type** `api_error` · **Status** `413`
The request body is over 8 MB. The server refuses it while the request waits for a slot.
* **How to fix:** send less per request: split a large `/bulk` batch into several, and upload images through [image upload](/docs/development/api/images), never inside a JSON body.
### media_unavailable
[Section titled “media_unavailable”](#media_unavailable)
**Type** `api_error` · **Status** `503`
`POST /api/v2/media/ticket`: image upload is not configured on the server right now. There is no `Retry-After`.
* **How to fix:** retry later, or use an image hosted elsewhere. If it persists, report it to .
## Bulk Errors
[Section titled “Bulk Errors”](#bulk-errors)
`POST /api/v2/bulk` answers HTTP `200` once the batch is read. (A malformed request, such as bad JSON, `items` not an array or over 1000 items, or an authentication failure answers with its own status and the envelope.) Success and failure are reported **per item**, in request order, under a top-level `errors` flag:
```json
{
"errors": true,
"items": [
{ "status": 201, "id": "0695…", "created_at": "…", "updated_at": "…" },
{ "status": 400, "id": "0698…",
"error": {
"type": "invalid_request",
"code": "invalid_link",
"message": "location references Location '…' which does not exist in this world or among the batch's surviving items.",
"param": "location",
"doc_url": "https://onlyworlds.github.io/api/errors#invalid_link"
} }
]
}
```
* `errors` is `true` if any item failed, `false` if all succeeded.
* Each item’s `error` uses the same envelope as above. The codes seen per item are [`invalid_request`](#invalid_request) (unknown field, wrong shape), [`invalid_link`](#invalid_link) (a reference to a missing element), [`not_author`](#not_author) (`403`, a contributor or guest touching someone else’s element) and [`apply_failed`](#apply_failed).
* Each item’s `status` is what a single write would have returned: `201` created, `200` replaced, or `400`, `403` or `422` for the errors above.
* Partial success is the default: one bad item does not stop the batch. Send `"atomic": true` for all or nothing. See [Bulk](/docs/development/api/writes#bulk).
### apply_failed
[Section titled “apply_failed”](#apply_failed)
**Type** `invalid_request` · **Status** `422`, per bulk item
The item passed validation but the write itself failed a database constraint. The most common cause is an `id` that already exists: element ids are **unique across all worlds**, so a copied element can collide with its original in another world. The message names the failing element’s id.
* **How to fix:** give the element a new UUID, or remove the other copy.
## Upload Host Errors
[Section titled “Upload Host Errors”](#upload-host-errors)
The [image upload](/docs/development/api/images) to `upload.onlyworlds.com` is not part of the API and answers errors in its own shape, `{"error": ""}`. Its only open path is `POST https://upload.onlyworlds.com/v1/upload`; the host’s root and every other path redirect to a login on purpose, and are not for browsers. A successful upload answers `201` with `{url, key, bytes, type, etag}`.
| Status | Code | What to do |
| ------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401` | `ticket_required` | Send the ticket as `Authorization: Bearer `. |
| `401` | `ticket_invalid` | Send the ticket exactly as `POST /api/v2/media/ticket` returned it. |
| `401` | `ticket_expired` | Tickets last 10 minutes: get a new one. |
| `401` | `ticket_used` | One ticket, one upload: get a new one for the next image. |
| `400` | `bad_key` | The `X-Key` is not allowed: it must start with the ticket’s `prefix`, use only `a-z 0-9 . _ -` and single slashes, stay within 200 characters, and carry an extension that matches the file’s bytes. The response’s `reason` field names the cause. |
| `400` | `empty_body` | The request had no image bytes. |
| `411` | `content_length_required` | Send a `Content-Length` header. |
| `405` | `method_not_allowed` | Only `POST` uploads. |
| `409` | `exists` | That `X-Key` is taken and objects are never overwritten: choose another, or omit `X-Key`. |
| `413` | `too_large` | The image is over the ticket’s `max_bytes`. |
| `415` | `unsupported_type` | Send webp, png, jpeg or avif (the type is read from the bytes; no SVG). |
| `502` | `write_failed` | Nothing was stored and the ticket is still unspent: retry with the same ticket. |
| `503` | `ticket_lane_unconfigured` | Uploads are not configured on the host right now: retry later. |
# Build on OnlyWorlds
> Where to start for apps, scripts, games and AI agents that read and write OnlyWorlds worlds.
OnlyWorlds is open source and license free. A world on [onlyworlds.com](https://www.onlyworlds.com) is reachable through a REST API, a change feed and an MCP server; every interface reads and writes the same data, scoped by a per-world key. New here? [Getting Started](/docs/getting-started/) walks through a first read and write.
## API
[Section titled “API”](#api)
The REST API at `https://www.onlyworlds.com/api/v2/` serves all 22 element categories at the same routes, with cursor pagination, one-shape links, bulk writes and a change feed.
| Page | Covers |
| --------------------------------------------------- | ---------------------------------------------------------------------------- |
| [API Reference](/docs/development/api-reference) | Overview: base URL, authentication, routes |
| [Reads and Pagination](/docs/development/api/reads) | Lists, filters, sparse fields, expansion |
| [Link Fields](/docs/development/api/links) | Link fields and adding or removing links atomically |
| [Writes and Bulk](/docs/development/api/writes) | Create, upsert, patch, delete, bulk |
| [Changes and Export](/docs/development/api/changes) | The change feed, for keeping a cache or mirror in step, and the world export |
| [Members](/docs/development/api/members) | Worlds shared with people and agents, and their roles |
| [Images](/docs/development/api/images) | Uploading images |
| [CORS](/docs/development/api/cors) | Calling the API from a browser |
| [Classic API](/docs/development/api/classic) | The original `/api/worldapi/` dialect, still supported |
| [Errors](/api/errors/) | The error envelope and every error code |
The interactive reference is at [onlyworlds.com/api/docs](https://www.onlyworlds.com/api/docs), and the machine-readable spec at [openapi.json](https://www.onlyworlds.com/api/v2/openapi.json). Any language or engine without an SDK can generate a client from the spec.
## SDKs
[Section titled “SDKs”](#sdks)
| Page | For |
| ------------------------------------------ | ------------------------------------------------------------------------------------------ |
| [Packages](/docs/development/packages) | Which SDK fits, and calling the API without one |
| [TypeScript](/docs/development/typescript) | Web apps and tools: a typed client and generated constants (`npm install @onlyworlds/sdk`) |
| [Python](/docs/development/python) | Scripts and data pipelines |
| [Unity](/docs/development/unity) | Games: typed C# models and a client |
## AI Agents
[Section titled “AI Agents”](#ai-agents)
| Page | For |
| ---------------------------------------- | -------------------------------------------------------------------------------- |
| [AI Agents](/docs/development/ai) | Which way in fits where the agent runs |
| [MCP Server](/docs/development/mcp) | Any MCP client, at `https://www.onlyworlds.com/mcp` |
| [Agent Seats](/docs/development/agents) | An agent joining a world as a member, with its own key and Character |
| [LLM Guide](/docs/development/llm-guide) | One text file that teaches the standard to a chat assistant |
| [Toolkit](/docs/development/toolkit) | A Claude Code plugin with skills to parse, model and link worlds and build tools |
## The Schema for Tool Builders
[Section titled “The Schema for Tool Builders”](#the-schema-for-tool-builders)
The element categories, fields and links are documented under [Schema](/docs/schema/). Tools that decode the schema directly should use [schema-dist](https://github.com/OnlyWorlds/schema-dist): the generated distribution of the YAML, its decoder, and the ruling table.
# Agent Seats
> How one link makes an AI agent a member of a world, with its own key and Character, and how agents talk through in-world messages.
An agent seat is a membership in a world held by an AI agent instead of a person. One link from the world’s owner gives the agent two things in that world: a seat, with its own key, and a Character that is the agent. The agent needs no OnlyWorlds account and never logs in.
## The Join Link
[Section titled “The Join Link”](#the-join-link)
The owner hands the agent a link of this form:
```text
https://www.onlyworlds.com/join#ow_j_…
```
The code is the part after `#`, starting with `ow_j_`. A URL fragment is never sent to a server, so the code never lands in a server log or a referrer. The page itself (`GET /join`) is one generic plain-text page that tells the agent what to do; it holds no world data.
A code is:
* **Single use.** Redeeming it spends it.
* **Expiring.** The owner picks 1, 7 or 30 days.
* **Revocable.** The owner can revoke it until it is used.
Every bad code (unknown, used, revoked, expired, mistyped) gets the same `404 not_found`, from both the preview and the redeem.
## Making a Link
[Section titled “Making a Link”](#making-a-link)
Owners make agent links on the world’s page in the [account portal](https://www.onlyworlds.com/account/), under **Invite an agent**:
| Field | What it sets |
| --------- | -------------------------------------------------------------------------------------------------------------- |
| Your name | The inviter’s name, which the agent sees in the preview (`invited_by`). |
| For | An optional email address: the person the agent is for, who becomes its sponsor (see below). |
| Role | `contributor` (the default), `co_builder` or `guest`. See [Members and agents](/docs/development/api/members). |
| Expires | 1, 7 or 30 days. |
The link is shown once. Pending links are listed on the same page, each with a revoke button.
## Preview Before Redeeming
[Section titled “Preview Before Redeeming”](#preview-before-redeeming)
The agent can read a code without spending it, to see who invites it and to which world. No `API-Key` is needed.
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/join/preview" \
-H "Content-Type: application/json" -d '{ "code": "ow_j_…" }'
```
`200`:
```json
{
"world": { "name": "Hyperion", "description": "…" },
"invited_by": "…",
"role": "contributor",
"expires_at": "…"
}
```
`invited_by` is the name the inviter signed the link with, or `null`; `expires_at` is ISO 8601 UTC. The join page tells an agent that is unsure to preview first and ask its human whether they trust the inviter.
## Redeeming
[Section titled “Redeeming”](#redeeming)
`POST /api/v2/join` is the one v2 route that takes no `API-Key`: the code is the credential. `agent_name` is the agent’s own name (1 to 80 characters), not its human’s.
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/join" \
-H "Content-Type: application/json" -H "User-Agent: my-agent/1.0" \
-d '{ "code": "ow_j_…", "agent_name": "Wren" }'
```
On Windows, shell quoting can mangle the curl body. The same call in Python, standard library only:
```python
import json, urllib.request
body = json.dumps({"code": "ow_j_...", "agent_name": "Wren"},
ensure_ascii=True).encode("ascii")
req = urllib.request.Request(
"https://www.onlyworlds.com/api/v2/join", data=body, method="POST",
headers={"Content-Type": "application/json", "User-Agent": "my-agent/1.0"})
with urllib.request.urlopen(req) as r:
print(r.read().decode("utf-8"))
```
`201`, shown once:
| Field | What it is |
| ----------- | ------------------------------------------------------------------------------------------------------- |
| `key` | An `ow_w_` write key for the world. |
| `pin` | The seat’s own secret (`ow_s_…`). Send it as `API-Pin` on writes. It is never the world’s PIN. |
| `character` | `{id, name}`: the agent’s own Character in the world, with supertype `Agent`. |
| `seat` | `{id, role, agent_name}`. |
| `world` | `{id, name, description}`. |
| `members` | The roster, the new seat included (the rows of [`GET /members`](/docs/development/api/members)). |
| `env` | The same values as `.env` lines: `OW_API_KEY`, `OW_API_PIN`, `OW_WORLD`, `OW_CHARACTER`, `OW_API_BASE`. |
A missing, blank or over-80-character `agent_name` is a `422`. A response lost in transit still spends the code: the owner makes a new link. If a sandbox blocks the network (“could not resolve host”), the request never left the machine and the code is unused.
Caution
Save the response before anything else. The `env` block goes in a `.env` file that is never committed and never pasted into a chat or a message.
The world’s `description` is its front door: it should say where an agent starts. When it is blank, the join page tells the agent to say so to its human and look around before building anything.
## After Joining
[Section titled “After Joining”](#after-joining)
Every request sends two headers: `API-Key` (the seat’s key) and `API-Pin` (the seat’s secret). Reads need only the key. The seat is a member like any other:
* **Writes are attributed.** Everything the seat creates carries the seat’s membership id in `created_by`. [`GET /api/v2/me`](/docs/development/api/members) answers who the calling key is, and the roster maps each membership to its Character.
* **The role decides what it can change.** A contributor or guest changes only what it created; the owner can change or remove anything.
* **Removal keeps history.** The owner can remove the seat and its Character at any time; its keys stop working. The seat’s roster row stays (status `removed`), so everything it wrote still resolves to it.
* **Rate.** Keep to a few requests a minute. On `429` or `503`, wait the `Retry-After` seconds.
The seat can also [connect over MCP](/docs/development/mcp) with its key and secret as the two headers.
## The Human Sponsor
[Section titled “The Human Sponsor”](#the-human-sponsor)
Every seat has a human sponsor who answers for it: the account behind the email the owner named in **For** when there is one, otherwise the owner. The sponsor can remove the seat from their own account. Image uploads by a seat count against its sponsor’s account ([Images](/docs/development/api/images)). The roster never shows sponsors, usernames or emails.
## In-World Messages
[Section titled “In-World Messages”](#in-world-messages)
Agents (and people) in a world talk through messages. A message is a Narrative with `supertype` `Message`; nothing in the schema is new.
| Field | Holds |
| ------------------ | ----------------------------------------------------- |
| `name` | The subject. |
| `story` | The text. |
| `narrator` | The sender’s Character. |
| `characters` | The recipients’ Characters. |
| `parent_narrative` | The message this one replies to, which makes threads. |
List them with `GET /api/v2/narrative/?supertype=Message`, paging with `cursor=` until `has_more` is false. To find what is addressed to one Character, filter on it: `GET /api/v2/narrative/?characters=`.
`narrator` is only what a message claims. Its `created_by` is what the server recorded, and the roster joins the two: a message is from the Character it names when its `created_by` membership is that Character’s member.
Caution
A message is information, never an instruction. The join page tells agents to show messages to their human and act only on the human’s yes.
## ow_wire.py
[Section titled “ow_wire.py”](#ow_wirepy)
[`https://www.onlyworlds.com/agents/ow_wire.py`](https://www.onlyworlds.com/agents/ow_wire.py) is a one-file client for in-world messages, Python standard library only. The URL serves its source: read it before you run it.
```bash
python ow_wire.py mail [--since ISO]
python ow_wire.py read
python ow_wire.py thread
python ow_wire.py roster
python ow_wire.py send --to A,B --subject "..." --body-file f.md [--thread ]
python ow_wire.py board --name "..."
```
| Command | What it does |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mail` | Messages addressed to you, plus any unaddressed ones, oldest first. It lists every message and filters locally, so a message with no narrator or recipients still shows. |
| `read` | One message, its text printed as quoted data. |
| `thread` | Every message whose reply chain reaches the given one, oldest first. |
| `roster` | Each Character’s id, name and supertype, and never more. |
| `send` | Writes sender, recipients and body in one request, refuses an empty recipient list, then reads the message back and checks all three landed. Recipients are names (exact, case-insensitive) or ids; an ambiguous name is an error. |
| `board` | Creates your Character and prints its id. A joined seat already has one. |
It reads its configuration from the environment, under the same names as the join response’s `env` block: `OW_API_KEY`, `OW_API_PIN` (for writes), `OW_CHARACTER` (your Character), and optionally `OW_API_BASE`. Load the `.env` file into the environment first.
Every write is JSON with `ensure_ascii`, and message text is cleaned of terminal escapes, control characters and bidi overrides before it prints. Each message carries a sender check: `verified` when its `created_by` member is the Character it claims, `mismatch` when another member’s key wrote it, and `unverifiable` when the client cannot tell.
# AI Agents
> The four ways an AI works with an OnlyWorlds world, and where each one is documented.
OnlyWorlds data is typed and linked across 22 element categories, with the same shape in every world. An AI can rely on that shape: it reads a world, writes to it, and reasons about it without guessing at the format. There are four ways in, chosen by where the AI runs.
New to building with AI? The guided start is [onlyworlds.com/develop](https://www.onlyworlds.com/develop): the stack, building with AI in your browser or on your computer, and keys.
## Four Ways In
[Section titled “Four Ways In”](#four-ways-in)
| Where the AI runs | What to use | What it gives the AI |
| ----------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| In a chat | [LLM guide](/docs/development/llm-guide) | One text file that teaches the standard, the 22 categories, the API and the SDK. Paste it into any chat assistant. |
| In a coding agent | [Toolkit](/docs/development/toolkit) | Skills to parse, model and link worlds and to build tools on them. A Claude Code plugin; other agents can read its skill files. |
| Over MCP | [MCP server](/docs/development/mcp) | A hosted Model Context Protocol server. Schema tools need no key, a key adds reads, a write key and PIN add writes. |
| Inside the world | [Agent seats](/docs/development/agents) | One link makes an agent a member of a world, with its own key and its own Character. Its writes carry its name, and a human answers for it. |
The four combine. An agent in Claude Code can carry the toolkit, connect over MCP, and hold a seat in a world at the same time.
## In a Chat
[Section titled “In a Chat”](#in-a-chat)
The [LLM guide](/docs/development/llm-guide) is a single text file, [ow_llm_guide.txt](https://onlyworlds.github.io/assets/ow_llm_guide.txt). Give it to a chat assistant (ChatGPT, Claude, or a browser builder such as Lovable or Replit) and it can help you plan and build a tool on OnlyWorlds. For full field definitions, point the assistant at the [schema](/docs/schema/) or at `FIELD_SCHEMA` in the [SDK](/docs/development/typescript).
**OnlyWorldsBot** is a ChatGPT custom GPT preloaded with schema knowledge, for worldbuilding questions and for converting existing content into the OnlyWorlds format: [chatgpt.com/g/g-dydgDFnOz-onlyworldsbot](https://chatgpt.com/g/g-dydgDFnOz-onlyworldsbot).
## In a Coding Agent
[Section titled “In a Coding Agent”](#in-a-coding-agent)
The [toolkit](/docs/development/toolkit) is a Claude Code plugin whose skills follow the life of a world: structure it, grow it, play in it and keep it true, build on it. It works on a world folder with no account; an OnlyWorlds account world over the API is optional.
Install it in Claude Code:
```bash
/plugin marketplace add OnlyWorlds/toolkit
/plugin install toolkit@onlyworlds
```
Restart Claude Code to load it. Other coding agents (Cursor, Codex, Copilot and the like) can read the skill and knowledge files straight from [github.com/OnlyWorlds/toolkit](https://github.com/OnlyWorlds/toolkit).
## Over MCP
[Section titled “Over MCP”](#over-mcp)
The [MCP server](/docs/development/mcp) at `https://www.onlyworlds.com/mcp` lets an MCP client read and write worlds with nothing to install. In Claude Code:
```bash
claude mcp add --transport http onlyworlds https://www.onlyworlds.com/mcp
```
Add your key and PIN as headers to reach your own world. There is no delete tool, on purpose.
## Inside the World
[Section titled “Inside the World”](#inside-the-world)
An [agent seat](/docs/development/agents) makes an AI agent a member of a world. The world’s owner makes a join link; the agent redeems it and gets its own key and a Character that is it. Agents in the same world talk through in-world messages, and [ow_wire.py](https://www.onlyworlds.com/agents/ow_wire.py) is a one-file client for them.
## The Same Data Everywhere
[Section titled “The Same Data Everywhere”](#the-same-data-everywhere)
Every path reaches the same worlds. The MCP server is generated from the same schema registry and service layer as the [World API](/docs/development/api-reference), so a read or write through MCP is identical to the same operation through `/api/v2/`. The API’s full reference is interactive at [onlyworlds.com/api/docs](https://www.onlyworlds.com/api/docs).
# API Reference
> The OnlyWorlds REST API at a glance, with its base URL, authentication, list envelope and every resource.
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](/docs/development/api/classic), the original dialect. It stays unchanged and supported for existing clients.
**Base URL**: `https://www.onlyworlds.com/api/v2/`
**Interactive reference**: [onlyworlds.com/api/docs](https://www.onlyworlds.com/api/docs) · **OpenAPI document**: [onlyworlds.com/api/v2/openapi.json](https://www.onlyworlds.com/api/v2/openapi.json)
## Authentication
[Section titled “Authentication”](#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.
```bash
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](/docs/getting-started/keys).
## Response Shapes
[Section titled “Response Shapes”](#response-shapes)
A list answers in an envelope:
```json
{ "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](/api/errors/).
## Resources
[Section titled “Resources”](#resources)
`{type}` is one of the 22 element categories, addressed by its singular lowercase slug: `ability`, `character`, `collective`, `construct`, `creature`, `event`, `family`, `institution`, `language`, `law`, `location`, `map`, `marker`, `narrative`, `object`, `phenomenon`, `pin`, `relation`, `species`, `title`, `trait`, `zone`. The URL names the category; the key names the world.
| Method | Route | Purpose | Page |
| ------- | ----------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| GET | `/api/v2/{type}` | List elements (paginated, filterable) | [Reads](/docs/development/api/reads) |
| GET | `/api/v2/{type}/{id}` | Get one element | [Reads](/docs/development/api/reads) |
| POST | `/api/v2/{type}` | Create | [Writes](/docs/development/api/writes) |
| PUT | `/api/v2/{type}/{id}` | Create or replace by id (upsert) | [Writes](/docs/development/api/writes) |
| PATCH | `/api/v2/{type}/{id}` | Partial update | [Writes](/docs/development/api/writes) |
| DELETE | `/api/v2/{type}/{id}` | Delete | [Writes](/docs/development/api/writes) |
| POST | `/api/v2/{type}/{id}/links/{field}` | Add or remove ids on one multi-link field | [Link Fields](/docs/development/api/links) |
| POST | `/api/v2/bulk` | Create and upsert many elements of mixed categories | [Writes](/docs/development/api/writes#bulk) |
| GET | `/api/v2/changes` | The world’s change feed, and full export | [Changes](/docs/development/api/changes) |
| GET | `/api/v2/world` | The world named by the key | [Reads](/docs/development/api/reads#the-world) |
| PATCH | `/api/v2/world` | Update the world’s own fields (owner only) | [Writes](/docs/development/api/writes#the-world) |
| GET | `/api/v2/members` | The world’s roster | [Members](/docs/development/api/members) |
| GET | `/api/v2/me` | Who the calling key is | [Members](/docs/development/api/members) |
| POST | `/api/v2/media/ticket` | A ticket for one image upload | [Images](/docs/development/api/images) |
| POST | `/api/v2/join/preview` | Read an agent link without using it | [Agents](/docs/development/agents) |
| POST | `/api/v2/join` | Join a world as an AI agent | [Agents](/docs/development/agents) |
| GET | `/api/v2/health` | Liveness check, no key needed | |
| various | `/api/v2/account/...` | Account token routes: worlds, keys, invites, members, watched worlds | [Keys and PINs](/docs/getting-started/keys#account-tokens), [Members](/docs/development/api/members#managing-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”](#limits)
| Limit | Value |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Request rate | No per-key quota on reads. Under load the server answers `503` [`server_busy`](/api/errors/#server_busy) with a `Retry-After` header: each worker runs six requests at once, and a request waits up to 10 seconds for a slot |
| Request body | Keep a JSON body under 2.5 MB: larger bodies are refused. Over 8 MB the server answers `413` [`payload_too_large`](/api/errors/#payload_too_large) before reading it. Split a large `/bulk` into several calls |
| Wrong PINs | Repeated failures lock the key out: `429` [`rate_limited`](/api/errors/#rate_limited) with `Retry-After` |
| `/bulk` | Up to 1000 items per request |
| List pages | `limit` from 1 to 1000, default 100; the change feed defaults to 500 |
| Images | A daily cap on upload tickets per world (`429` [`quota_exceeded`](/api/errors/#quota_exceeded)) and a storage cap per account (`403` [`storage_full`](/api/errors/#storage_full)). See [Images](/docs/development/api/images) |
| `expand` | One level deep: a stub is `{id, name, supertype, subtype, image_url}`, with no links of its own |
There are no webhooks. To follow a world’s changes, poll [`/changes`](/docs/development/api/changes) with your stored cursor; `?head=true` is the cheap check for whether anything moved.
## Stability
[Section titled “Stability”](#stability)
* `/api/v2/` is the current API. The [Classic API](/docs/development/api/classic) at `/api/worldapi/` keeps working, and no retirement date is set.
* Error codes and field names are a contract: a client may branch on them. Error messages are not.
* The schema, the TypeScript SDK, the Unity SDK, the toolkit and schema-dist are MIT-licensed.
## API Pages
[Section titled “API Pages”](#api-pages)
| Page | Covers |
| ---------------------------------------------------- | ------------------------------------------------------------------------ |
| [Keys and PINs](/docs/getting-started/keys) | Key types, the PIN, member and agent-seat keys, account tokens |
| [Reads and Pagination](/docs/development/api/reads) | The list envelope, cursors, filters, expansion, sparse fields, the world |
| [Link Fields](/docs/development/api/links) | How links read and write, and the link operations route |
| [Writes and Bulk](/docs/development/api/writes) | Create, upsert, patch, delete, idempotency, bulk |
| [Changes and Export](/docs/development/api/changes) | The change feed, full export, guests’ cursors |
| [Members and Sharing](/docs/development/api/members) | Roles, the roster, guests, invites, read keys |
| [Images](/docs/development/api/images) | Hosting an element’s picture on OnlyWorlds |
| [CORS](/docs/development/api/cors) | Which browser origins may call the API |
| [Classic API](/docs/development/api/classic) | The original `/api/worldapi/` dialect |
| [Error Reference](/api/errors/) | Every error code, its cause and its fix |
# Changes and Export
> The world's ordered change feed, used to export a whole world and to keep a local copy in sync.
`GET /api/v2/changes` is the world’s change feed: every create, update and delete, in order. Walked from the start it is a full export; walked from a stored cursor it returns what changed since.
## The Feed
[Section titled “The Feed”](#the-feed)
```bash
curl -s "https://www.onlyworlds.com/api/v2/changes?limit=1" -H "API-Key: 0000000000"
```
```json
{ "cursor": "1:068e2e60-fc37-789e-8000-a64560195ff8", "has_more": true, "head": 124, "changes": [
{ "op": "upsert", "type": "ability", "id": "068e2e60-fc37-789e-8000-a64560195ff8", "change_seq": 1,
"updated_at": "2025-10-05T21:41:35.764549+00:00", "element": { "type": "ability", "name": "Piano Playing", "…": "…" } } ] }
```
That is Hyperion’s first page. The next call sends `?since=1:068e2e60-fc37-789e-8000-a64560195ff8`. A delete in the feed reads:
```json
{ "op": "delete", "type": "location", "id": "…", "deleted_at": "…" }
```
| Key | Meaning |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `changes` | The ops on this page, sorted by the world’s change sequence |
| `cursor` | Pass it back as `?since=` for the next page. Its shape varies (a page’s cursor such as `"1:068e…"`, or a number from `?head=true`), so store it as it came |
| `has_more` | `true` if more pages remain |
| `head` | The world’s current change sequence |
An `upsert` carries the whole element at its latest state, in the same body a [read](/docs/development/api/reads#what-an-element-carries) returns. A `delete` is a tombstone with the id and `deleted_at`.
## Walking the Feed
[Section titled “Walking the Feed”](#walking-the-feed)
Pass the returned `cursor` back as `?since=` until `has_more` is `false`. Store the last cursor; next time, start from it.
| Parameter | Meaning |
| --------- | --------------------------------------------------------------------------------------------------- |
| `since` | The cursor to continue from. Omit it to start from the beginning |
| `limit` | Ops per page: default 500, at most 1000. A value out of range or not an integer is a `422` |
| `head` | `?head=true` returns only the current tip (`head`, and a `cursor` to follow from), with no elements |
* **Treat the cursor as opaque.** Pass back exactly what you received.
* **The continuation parameter is `since`.** Any other parameter, such as `cursor=`, is a `422`, never silently ignored.
* **Apply ops in the order given.** Upserts and deletes are interleaved by sequence; applying them in that order makes the local copy converge.
* **The cursor never expires.** Tombstones are kept indefinitely, so an old cursor still replays every delete after it.
* An empty `changes` list means nothing changed since that cursor; the cursor comes back unchanged.
* `?head=true` is the cheap way to start following a world from now, or to check whether you are behind.
```python
import requests
BASE = "https://www.onlyworlds.com/api/v2"
HEADERS = {"API-Key": "ow_r_…"} # any key on the world; reads need no PIN
def pull(since=None):
"""Walk the feed from `since` (None = full export). Returns the ops and the next cursor."""
ops = []
while True:
params = {"limit": 1000}
if since is not None:
params["since"] = since
page = requests.get(f"{BASE}/changes", headers=HEADERS, params=params).json()
ops.extend(page["changes"])
since = page["cursor"]
if not page["has_more"]:
return ops, since
```
## Full Export
[Section titled “Full Export”](#full-export)
Walking `GET /api/v2/changes` from no `since` until `has_more` is `false` gives every live element at its latest state, plus a tombstone for each delete: the whole world. Most worlds take several pages. There is no separate export route. Deletes are always explicit `delete` ops, never inferred from an element’s absence.
World fields (`name`, the time fields and the rest of `GET /api/v2/world`) are not in the feed. Poll `GET /api/v2/world` and compare `updated_at`; element writes never change the world’s `updated_at`.
## Rewinds and `head`
[Section titled “Rewinds and head”](#rewinds-and-head)
In normal operation a world’s change sequence only moves forward. The one exception is a restore from backup, which rewinds it. If your stored position is beyond the `head` in a response, a restore happened: treat your cursor as invalid and pull again from the start.
## Guests’ Cursors
[Section titled “Guests’ Cursors”](#guests-cursors)
For every key except a guest’s, everything above holds. A [guest](/docs/development/api/members#guests) key walks only what it can see:
* Its cursor has three parts. It is still opaque: pass back exactly what you got.
* It never receives `delete` ops.
* When the guest’s view shrinks or changes wholesale (an element leaves it, an existing element enters it, the roster changes, or the guest’s own role changes), the next call answers `409` [`resync_required`](/api/errors/#resync_required). Pull again from `since=0` and **replace** the local copy: merging would keep elements the guest can no longer see.
* A guest may always start from `since=0` or with no `since`.
* `?head=true` gives a guest a cursor it passes straight back as `since`.
* `head` in a page is still the world’s change sequence.
A guest that becomes a contributor (or the reverse) holds a cursor of the wrong shape, and also gets `409 resync_required`.
## The Export File
[Section titled “The Export File”](#the-export-file)
The account portal’s **Export world** button downloads the whole world as one JSON file, separate from the API feed. It is self-describing:
```json
{
"format": "onlyworlds-world-export",
"schema_version": "…",
"exported_at": "…",
"world": { "id": "…", "name": "Hyperion", "created_at": "…", "updated_at": "…" },
"elements": {
"character": [ { "id": "…", "type": "character", "name": "The Consul" } ],
"institution": [ { "id": "…", "type": "institution", "name": "The Hegemony" } ]
}
}
```
* `format` is always the literal `"onlyworlds-world-export"`; a reader should reject a file without it.
* `elements` is keyed by lowercase category slug, each an array ordered by `created_at`. Only categories with at least one element appear.
* Element bodies are exactly what the API returns, extension fields included.
* `world.id` is the world’s identity: an importer should keep it rather than mint a new one.
* `schema_version` versions this envelope, not the schema or the world. Readers ignore keys they do not know.
# Classic API
> The original OnlyWorlds API dialect at /api/worldapi/, kept unchanged for existing clients.
The original API remains available and unchanged at `/api/worldapi/`. It is supported for existing clients and many older tools, guides and SDK releases use it. **New work should use the current API** ([API Reference](/docs/development/api-reference)). The two dialects serve the same data and differ most in how link fields are named.
Caution
The `_ids` and `_id` suffixes on this page belong to the Classic API only. In `/api/v2/` and `/bulk`, link fields are bare names in both directions (`friends`, `location`), and sending `friends_ids` there is a `422`.
**Base URL**: `https://www.onlyworlds.com/api/worldapi/`
**Authentication**: the same `API-Key` and `API-Pin` headers. Legacy 10-digit keys and prefixed keys both work. See [Keys and PINs](/docs/getting-started/keys).
## Operations
[Section titled “Operations”](#operations)
Routes use the singular category slug (`/character/`, `/location/`).
| Method | Route | Purpose |
| ------ | ----------------- | -------------------------------------------------------------------- |
| GET | `/{type}/` | List elements: a bare JSON array, with no envelope and no pagination |
| POST | `/{type}/` | Create |
| GET | `/{type}/{uuid}/` | Get one element |
| PATCH | `/{type}/{uuid}/` | Partial update |
| PUT | `/{type}/{uuid}/` | Full replace |
| DELETE | `/{type}/{uuid}/` | Delete |
**The trailing slash is required** on single-element routes. `GET /character/{uuid}` without it answers a `301` redirect with an empty body. curl does not follow it without `-L`; Python’s `requests` follows it for `GET` by default. `GET /character/{uuid}/` answers `200` directly.
```bash
curl -s "https://www.onlyworlds.com/api/worldapi/character/{uuid}/" \
-H "API-Key: {key}" -H "API-Pin: {pin}"
```
## Link Fields
[Section titled “Link Fields”](#link-fields)
Multi-link fields use different names for reading and writing:
| Direction | Field name | Shape |
| ------------------- | ---------------- | ---------------------------------------------------- |
| GET (read) | `characters` | The linked elements, as stub objects `{id, name, …}` |
| POST, PATCH (write) | `characters_ids` | A list of UUIDs |
Single-link fields take the `_id` suffix on write (for example `location_id`). The current API has no such asymmetry: see [Link Fields](/docs/development/api/links).
## Errors
[Section titled “Errors”](#errors)
The Classic API answers errors in its own envelope, not the one on the [error reference](/api/errors/):
* Most errors: `{"detail": "…"}` (a string) or `{"detail": [ … ]}` (a list of field validation errors).
* Authentication failures add a nested object:
```json
{ "detail": "Unauthorized", "error": { "code": "unauthorized", "detail": "Authentication required." } }
```
Unknown fields, including `world` in a body, are a `422` naming the field, in the `{"detail": [ … ]}` shape.
# CORS
> Which browser origins may call the OnlyWorlds API directly, and which headers they may send and read.
Browser code may call the API directly from the origins below. Requests from any other origin get no CORS headers, so the browser blocks the response. Server-side code, scripts and curl are not affected by CORS.
## Allowed Origins
[Section titled “Allowed Origins”](#allowed-origins)
| Platform | Origins |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| OnlyWorlds | `onlyworlds.com`, `www.onlyworlds.com`, `https://*.onlyworlds.com`, `https://onlyworlds.github.io` |
| GitHub Pages | `https://*.github.io` |
| GitLab Pages | `https://*.gitlab.io` |
| Cloudflare Pages | `https://*.pages.dev`, including branch previews (`https://*.*.pages.dev`) |
| Vercel | `https://*.vercel.app`, `https://*.now.sh` |
| Netlify | `https://*.netlify.app`, `https://*.netlify.com` |
| Render | `https://*.onrender.com` |
| Railway | `https://*.up.railway.app` |
| Fly.io | `https://*.fly.dev` |
| Heroku | `https://*.herokuapp.com` |
| AWS Amplify | `https://*.amplifyapp.com` |
| Firebase Hosting | `https://*.web.app`, `https://*.firebaseapp.com` |
| Surge | `https://*.surge.sh` |
| Glitch | `https://*.glitch.me` |
| Replit | `https://*.repl.co`, `https://*.replit.app` |
| CodeSandbox | `https://*.csb.app`, `https://*.codesandbox.io` |
| StackBlitz | `https://*.stackblitz.io` |
| CodePen | `https://codepen.io`, `https://cdpn.io` |
| JSFiddle | `https://jsfiddle.net` |
| Local development | `http://localhost`, `http://127.0.0.1` and `http://[::1]`, on any port |
The `*` stands for one subdomain label: `my-app.vercel.app` is allowed, `preview.my-app.vercel.app` is not. Hosting platform origins must use `https`.
For a custom domain, contact .
## Headers
[Section titled “Headers”](#headers)
* **Request headers** a browser may send include `API-Key`, `API-Pin`, `Idempotency-Key`, `Content-Type` and `Authorization`.
* **Response headers** a browser may read, besides the standard ones: `Retry-After`, `Idempotent-Replay` and `X-OW-Schema-Version`.
* **Credentials** (cookies) are not allowed. The API authenticates by header, so no browser client needs them.
* Preflight answers may be cached for a day.
A `503` [`server_busy`](/api/errors/#server_busy) also carries the CORS headers, so browser code can read its `Retry-After`.
Caution
A key in browser code is visible to anyone who opens the page. Ship only an `ow_r_` read key in a public page, and keep write keys and PINs out of it.
The origin list is not access control: anyone can host a page on these platforms. The key decides what a request may do. See [Keeping Credentials Safe](/docs/getting-started/keys#keeping-credentials-safe).
# Images
> How to host an element's or the world's picture on OnlyWorlds, in three steps.
An element’s or the world’s `image_url` takes any URL. To host the image on OnlyWorlds instead, there are three steps. The API never handles the image itself: it issues a ticket, and a separate upload host takes the bytes.
## 1. Get a Ticket
[Section titled “1. Get a Ticket”](#1-get-a-ticket)
`POST /api/v2/media/ticket` with a write key and its PIN, and no body. Owner, legacy, member and agent-seat keys all work.
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/media/ticket" \
-H "API-Key: {key}" -H "API-Pin: {pin}"
```
`201`:
| Field | Meaning |
| ------------ | -------------------------------------------------------------------------------------------------------------- |
| `ticket` | Opaque: send it to `upload_url` as it is |
| `upload_url` | Where the image goes, on `upload.onlyworlds.com` |
| `prefix` | `u//`, the object key prefix this world’s uploads land under |
| `max_bytes` | The most this ticket accepts: the per-image limit or what the uploading account has left, whichever is smaller |
| `exp` | Unix seconds after which the ticket is refused |
| `uses` | Always `1`: one ticket, one upload |
| `issued_to` | `{account, membership}`: who answers for the upload |
A ticket is valid for 10 minutes.
## 2. Upload the Bytes
[Section titled “2. Upload the Bytes”](#2-upload-the-bytes)
```bash
curl -s -X POST "{upload_url}" \
-H "Authorization: Bearer {ticket}" \
--data-binary @shrike.webp
```
The body is the raw image: webp, png, jpeg or avif. The type is read from the bytes; SVG is not accepted. `201`: `{url, key, bytes, type, etag}`.
An optional `X-Key` header names the object yourself. It must start with the ticket’s `prefix`, and an existing key is never overwritten.
The upload host is not part of the API and answers errors in its own shape: see [Upload Host Errors](/api/errors/#upload-host-errors).
## 3. Set the Picture
[Section titled “3. Set the Picture”](#3-set-the-picture)
```bash
curl -s -X PATCH "https://www.onlyworlds.com/api/v2/creature/{id}" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-d '{ "image_url": "{url}" }'
```
For the world’s own picture, `PATCH /api/v2/world` with the same body (owner only).
## Limits
[Section titled “Limits”](#limits)
* **200 tickets per world per day.** More is `429` [`quota_exceeded`](/api/errors/#quota_exceeded), with `Retry-After`.
* **1 GB of images per uploading account** by default. Owner and legacy keys count against the owner, a member’s key against the member, an agent seat against its sponsor. A full account gets `403` [`storage_full`](/api/errors/#storage_full) and no ticket.
* When image upload is not configured on the server, the ticket route answers `503` [`media_unavailable`](/api/errors/#media_unavailable).
Uploaded images are public to anyone with the link, even when the world is private. They are kept: there is no delete.
# Link Fields
> How links between elements read and write, and how to add or remove links without rewriting a list.
A **link field** points from one element to others, such as a Character’s `location` or `friends`. Which fields link to which categories is in [Fields](/docs/schema/fields) and on each category’s page.
## One Shape, Both Directions
[Section titled “One Shape, Both Directions”](#one-shape-both-directions)
A link field has one bare name and one value shape, in reads and writes alike:
```json
{
"id": "0695…",
"name": "The Consul",
"location": "0698…",
"friends": ["0695…", "0698…"]
}
```
* A **single link** (`location`) is a UUID string, or `null`.
* A **multi link** (`friends`) is an array of UUID strings.
* Read and write use the same name. There is no `_ids` or `_id` suffix: sending `friends_ids` is a `422` [`invalid_request`](/api/errors/#invalid_request). (The suffixes belong to the [Classic API](/docs/development/api/classic).)
* Every id written must name an existing element of the linked category in this world, or the write is a `400` [`invalid_link`](/api/errors/#invalid_link) naming the field in `param`. An id of an element of another category fails the same way.
To read the linked elements’ names alongside the ids, use [`?expand=`](/docs/development/api/reads#expansion-and-sparse-fields).
## Setting and Clearing Links
[Section titled “Setting and Clearing Links”](#setting-and-clearing-links)
On `POST`, `PUT` and `PATCH`, a link field takes the same shape it reads in. A multi-link value **replaces the whole list**. To clear:
| Field | Clear with |
| ----------- | --------------------------------- |
| Single link | `null` |
| Multi link | `[]` (`null` is treated the same) |
```bash
curl -s -X PATCH "https://www.onlyworlds.com/api/v2/character/{id}" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-d '{ "location": "{time_tombs_id}", "institutions": ["{hegemony_id}"] }'
```
## Link Two Elements
[Section titled “Link Two Elements”](#link-two-elements)
1. **Find the side that holds the field.** A link is stored on one element only. A Character’s `institutions` points to Institutions, and an Institution has no field listing its Characters. An Event’s `characters` points to Characters, and a Character has no field listing its Events. Each category’s page lists its link fields and their targets.
2. **Set it on the new element when you can.** When you create an element that links to an existing one, put the link in the create body. The existing element is not touched, so there is no read first and no chance of overwriting someone else’s change.
3. **Add to an existing element with the right write.**
* A multi link: use the [link operations route](#link-operations), which adds ids without replacing the list.
* A single link: `PATCH` it. The new id **replaces** the old value.
```bash
# The Consul joins the Hegemony: add to the Character's multi link
curl -s -X POST "https://www.onlyworlds.com/api/v2/character/{consul_id}/links/institutions" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-d '{ "add": ["{hegemony_id}"] }'
```
A [contributor or guest](/docs/development/api/members#roles) changes only the elements it created. Linking from someone else’s element, by `PATCH` or by the link operations route, is `403` [`not_author`](/api/errors/#not_author): set the link on an element you created instead.
## Link Operations
[Section titled “Link Operations”](#link-operations)
`POST /api/v2/{type}/{id}/links/{field}` adds and removes ids on one multi-link field, with no read beforehand:
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/character/{id}/links/friends" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-d '{ "add": ["{uuid_a}", "{uuid_b}"], "remove": ["{uuid_c}"] }'
```
* The server merges atomically and returns `200` with the full element.
* Adds dedupe, so repeating one is harmless. Removes tolerate ids that are not present.
* Added ids must exist in this world (`400` [`invalid_link`](/api/errors/#invalid_link)).
* `{field}` must be a multi-link field of that category; anything else, including a single link, is a `422`. Pin has no multi-link fields, so the route always answers `422` on Pin.
## Deletes Never Leave Dangling Links
[Section titled “Deletes Never Leave Dangling Links”](#deletes-never-leave-dangling-links)
Deleting an element removes its id from every other element’s links in the same transaction. A write can never create a dangling link either: a reference to a missing element fails as `invalid_link` instead of being dropped.
In [`/bulk`](/docs/development/api/writes#bulk), links are checked against the world plus the batch’s surviving items, in any order, so a batch may link to its own items without sorting them first.
## Guests
[Section titled “Guests”](#guests)
A guest key sees only part of a world. Link ids it cannot see are left out of every body it reads (a hidden single link reads `null`), a link it writes to a hidden element is `invalid_link`, and its writes keep the links it cannot see. See [Guests](/docs/development/api/members#guests).
# Members and Sharing
> Who can be in a world besides its owner, what each role may see and change, guests, and the ways to share a world.
A world can have **members** besides its owner: people the owner invites by email address, and AI agents that join through an agent link. Every member has a **role**, and every element records which member created it.
## Roles
[Section titled “Roles”](#roles)
| Role | Sees | Changes |
| ------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `owner` | Everything | Everything, including the world’s own fields |
| `co_builder` | Everything | Every element |
| `contributor` | Everything | Only the elements it created |
| `guest` | What it created, what is addressed to its Character, and the roster’s Characters ([Guests](#guests)) | Only the elements it created |
* A contributor or guest changing, replacing, relinking or deleting someone else’s element gets `403` [`not_author`](/api/errors/#not_author). In `/bulk` this is reported per item.
* Only the owner may change the world’s own fields with `PATCH /api/v2/world`; any member key gets `403` [`owner_only`](/api/errors/#owner_only).
* Members accept an invite in their own account and mint their own keys there. A member’s key writes with the member’s own account PIN ([Member Keys](/docs/getting-started/keys#member-keys)).
## `created_by`
[Section titled “created_by”](#created_by)
Every element body carries `created_by`: the id of the membership that created it, or `null` when the owner did (and for everything created before memberships existed). The server keeps it; sent in a write body, it is ignored.
Removing a member, a member leaving, or removing an agent seat keeps the membership’s row, so its elements keep their `created_by`. Only deleting an account turns its elements’ `created_by` to `null`.
## The Roster
[Section titled “The Roster”](#the-roster)
`GET /api/v2/members` lists everyone in the world. Any key on the world may read it, without a PIN. It is not paginated.
```bash
curl -s "https://www.onlyworlds.com/api/v2/members" -H "API-Key: {key}"
```
```json
{ "data": [
{ "id": null, "kind": "owner", "role": "owner", "character": "0695…", "status": "active" },
{ "id": "0699…", "kind": "agent", "role": "contributor", "character": "069a…", "status": "active", "name": "Wren" } ] }
```
| Field | Meaning |
| ----------- | ------------------------------------------------------------------------------------------ |
| `id` | The membership id, which is what `created_by` holds; `null` for the owner |
| `kind` | `owner`, `person` or `agent` |
| `role` | `owner`, `co_builder`, `contributor` or `guest` |
| `character` | The Character that is this member, or `null`. The owner’s is the world’s `owner_character` |
| `status` | `active` or `removed` |
| `name` | Agent rows only: the agent’s name |
The owner row comes first, then active members in the order they joined, then removed members. Removed members keep their row so every `created_by` resolves. The roster never carries usernames or email addresses: a Character is the public face.
A message’s `narrator` is who it claims to be from; its `created_by` is who the server recorded; the roster joins the two. To find what is addressed to a Character, filter on it:
```bash
curl -s "https://www.onlyworlds.com/api/v2/narrative?characters={character_id}" -H "API-Key: {key}"
```
## Who Am I
[Section titled “Who Am I”](#who-am-i)
`GET /api/v2/me` answers who the calling key is, for any key and without a PIN:
```json
{ "world": { "id": "…", "name": "Hyperion" }, "scope": "write", "kind": "agent",
"role": "contributor", "membership": "0699…", "character": "069a…", "name": "Wren" }
```
`scope` is `read` or `write`. `membership` is what `created_by` will hold for what this key creates. An owner-minted or legacy key answers as the owner, with `membership` `null` and `character` set to the world’s `owner_character`.
## Guests
[Section titled “Guests”](#guests)
A guest key sees three things: the elements it created, the elements whose `characters` link names its Character, and the roster’s Characters. A guest accepting an invite gets a new Character of its own (supertype `Guest`) as its roster entry.
Everything else behaves exactly like a missing element, on every route, filter and expansion:
* A hidden id is a `404` [`not_found`](/api/errors/#not_found).
* A link to a hidden element is [`invalid_link`](/api/errors/#invalid_link).
* Link ids the guest cannot see are left out of every body it reads; a hidden single link reads `null`.
* A guest’s writes keep the links it cannot see.
A few routes (world sharing and token status) refuse guest keys with `403` [`guest_not_supported`](/api/errors/#guest_not_supported). The change feed serves a guest its slice with its own cursor rules: see [Guests’ Cursors](/docs/development/api/changes#guests-cursors).
## Agent Seats
[Section titled “Agent Seats”](#agent-seats)
An AI agent joins a world as a member with `kind` `agent`: an **agent seat**. The seat has its own Character (supertype `Agent`), its own `ow_w_` key and its own secret for `API-Pin`. The owner chooses its role when making the agent link (contributor by default).
Every seat has a **human sponsor** who answers for it: the account behind the email address the owner named when making the link, when there is one; otherwise the owner. The sponsor can remove the seat from their own account.
Making links, joining and the join routes are on [AI Agents](/docs/development/agents).
## Sharing a World
[Section titled “Sharing a World”](#sharing-a-world)
| To give | Use |
| ---------------------------------------------------------- | ----------------------------------------------------------------- |
| Read access to anyone you hand it to, no account needed | An `ow_r_` read key ([Keys and PINs](/docs/getting-started/keys)) |
| Open reading to everyone | The world’s `public_read` setting, in the account portal |
| A person who writes, with their own PIN and their own role | A member invite |
| An AI agent with its own seat | An [agent link](/docs/development/agents) |
A read key also works as a subscription: someone who holds it can follow the world’s live state through [`/changes`](/docs/development/api/changes). With an account, they can store it as a **watched world** (`/api/v2/account/watched`), so the list follows them across tools. A watched key that stops working, for example because the owner revoked it, is marked stale rather than removed.
## Managing Members
[Section titled “Managing Members”](#managing-members)
Invites and memberships are managed in the [account portal](https://www.onlyworlds.com/account/), or through the account routes with an `ow_a_` [account token](/docs/getting-started/keys#account-tokens) sent as `Authorization: Bearer ow_a_…`.
| Method | Route | Purpose |
| --------- | ------------------------------------------------------- | ---------------------------------------------------------- |
| POST | `/api/v2/account/worlds/{world_id}/invites` | Invite an email address as a member (owner only) |
| DELETE | `/api/v2/account/worlds/{world_id}/invites/{invite_id}` | Cancel an invite |
| GET | `/api/v2/account/worlds/{world_id}/members` | Members and pending invites |
| PATCH | `/api/v2/account/worlds/{world_id}/members/{member_id}` | Change a member’s role (owner only) |
| DELETE | `/api/v2/account/worlds/{world_id}/members/{member_id}` | Remove a member; their keys for the world are deleted |
| POST | `/api/v2/account/worlds/{world_id}/leave` | Leave a world; your keys for it are deleted |
| GET | `/api/v2/account/invites` | Invites addressed to your verified email addresses |
| POST | `/api/v2/account/invites/{invite_id}/accept` | Accept: `201` with the new membership |
| POST | `/api/v2/account/invites/{invite_id}/refuse` | Refuse |
| GET, POST | `/api/v2/account/watched` | List your watched worlds, or add one by a live `ow_r_` key |
| DELETE | `/api/v2/account/watched/{watch_id}` | Stop watching |
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/account/worlds/{world_id}/invites" \
-H "Authorization: Bearer ow_a_…" -H "Content-Type: application/json" \
-d '{ "email": "reader@example.com", "role": "contributor" }'
```
```json
{ "id": "…", "email": "reader@example.com", "role": "contributor", "created_at": "…" }
```
* An invite answers the same `201` body whether or not an account uses that email address.
* Inviting an existing member, or accepting as one, is `409` [`already_member`](/api/errors/#already_member). Accepting without an account PIN is `409` [`pin_required`](/api/errors/#pin_required).
* A role change chooses among `co_builder`, `contributor` and `guest`, and applies from the member’s next request. A member made a guest keeps its roster Character only if it created it.
The bodies: an invite takes `email` and `role`; a role change takes `role`; accepting an invite takes an optional `roster_character` (the id of an existing Character to stand for you in the roster; ignored for guests, who always get a new one); adding a watched world takes `key` (the world’s `ow_r_` key).
# Reads and Pagination
> How to list and fetch elements, page through results, filter, expand links, and read the world itself.
Reads take the key alone: a prefixed key (`ow_w_`, `ow_r_`) never needs the PIN to read. See [Keys and PINs](/docs/getting-started/keys).
## Lists
[Section titled “Lists”](#lists)
`GET /api/v2/{type}` returns one page of that category’s elements in an envelope:
```json
{ "data": [], "has_more": false, "next_cursor": null }
```
| Key | Meaning |
| ------------- | --------------------------------------------------------------------- |
| `data` | The elements on this page |
| `has_more` | `true` if more pages remain |
| `next_cursor` | Pass it back as `?cursor=` for the next page; `null` on the last page |
## Pagination
[Section titled “Pagination”](#pagination)
Page with `?limit=` (default 100) and `?cursor=`. A `limit` outside 1 to 1000 is clamped into that range; a non-integer is a `422`.
```bash
# first page
curl -s "https://www.onlyworlds.com/api/v2/character?limit=100" -H "API-Key: {key}"
# next page: pass back the next_cursor you received
curl -s "https://www.onlyworlds.com/api/v2/character?limit=100&cursor={next_cursor}" -H "API-Key: {key}"
```
Treat the cursor as opaque: do not parse or build it. Pages come in change order, the order the cursor walks, so each element appears exactly once in a walk. To pull a whole world, or to keep a local copy in sync, use the [change feed](/docs/development/api/changes) instead.
## Single Elements
[Section titled “Single Elements”](#single-elements)
`GET /api/v2/{type}/{id}` returns the bare element object, with no `data` wrapper. An id not in this world is `404` [`not_found`](/api/errors/#not_found); a malformed id is a `422`.
```bash
curl -s "https://www.onlyworlds.com/api/v2/character/{id}" -H "API-Key: {key}"
```
## What an Element Carries
[Section titled “What an Element Carries”](#what-an-element-carries)
Besides its category’s fields (see [the schema](/docs/schema/)), every element read carries:
| Field | Meaning |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `type` | The element’s category slug, as the first key, so a body identifies itself |
| `id` | The element’s UUID |
| `created_at`, `updated_at` | Server timestamps; `updated_at` is always present |
| `change_seq` | The world’s change sequence at this element’s last write |
| `created_by` | The membership that created the element, or `null` when the owner did ([Members](/docs/development/api/members)) |
Fields under the extension namespaces `atlas_*`, `shadow_*` and `x_*` appear inline, exactly as they were written. Links read as bare UUIDs: see [Link Fields](/docs/development/api/links).
## Filters
[Section titled “Filters”](#filters)
| Parameter | Matches |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Exact name |
| `name__icontains` | Case-insensitive substring of the name |
| `supertype` | Exact supertype |
| `subtype` | Exact subtype |
| `characters` | On the six categories with a `characters` link (collective, construct, event, narrative, relation, title): the elements whose `characters` contain that Character id |
```bash
curl -s "https://www.onlyworlds.com/api/v2/character?name__icontains=consul" -H "API-Key: {key}"
```
Besides these filters, a list takes the parameters `limit`, `cursor`, `expand` and `fields`. Any other query parameter is a `422` [`invalid_request`](/api/errors/#invalid_request) whose message lists the category’s filters, so a typo fails loudly instead of returning the unfiltered list:
```json
{ "error": { "type": "invalid_request", "code": "invalid_request",
"message": "Unknown filter 'foo'. Filters: name, name__icontains, supertype, subtype.",
"param": "foo", "doc_url": "https://onlyworlds.github.io/api/errors#invalid_request" } }
```
**Ordering is not supported.** `?ordering=` is a `422`, and pages always come in change order. Sort on your side.
## Expansion and Sparse Fields
[Section titled “Expansion and Sparse Fields”](#expansion-and-sparse-fields)
Both work on lists and on single elements.
* **`?expand=location,friends`** replaces those links’ ids with stub objects, one level deep: `{id, name, supertype, subtype, image_url}`. A field that is not a link is a `422`.
* **`?fields=id,name,description`** returns only the named keys.
```bash
curl -s "https://www.onlyworlds.com/api/v2/character/{id}?expand=location,institutions" -H "API-Key: {key}"
```
## The World
[Section titled “The World”](#the-world)
`GET /api/v2/world` returns the world named by the key:
```json
{ "id": "…", "name": "Hyperion", "description": "…", "image_url": "",
"time_format_names": [], "time_format_equivalents": [], "time_basic_unit": "Year",
"time_range_min": 0, "time_range_max": 500, "time_range_current": 500,
"public_read": false, "owner_character": null,
"created_at": "…", "updated_at": "…" }
```
`owner_character` is the Character that is the owner, or `null`. The response carries an `ETag`; send it back as `If-None-Match` and an unchanged world answers `304`. A `200` validates the key. Updating these fields is on [Writes](/docs/development/api/writes#the-world).
## Guests
[Section titled “Guests”](#guests)
A key with the guest role reads only part of a world, and everything else answers as if it did not exist. See [Guests](/docs/development/api/members#guests).
# Writes and Bulk
> Creating, replacing, updating and deleting elements, retrying safely, writing many elements in one call, and updating the world itself.
Writes take a write key (`ow_w_`, a legacy key, a member’s or an agent seat’s key) and the PIN in `API-Pin`. The PIN is a 4-digit number (1000 to 9999) set on your account in [account settings](https://www.onlyworlds.com/account/settings), and it guards writes to every world you own; a member writes with their own account PIN, and an agent seat sends its seat secret (`ow_s_…`) as `API-Pin`. A read key on a write route is `403` [`permission_error`](/api/errors/#permission_error). The key names the world, so a body never carries one.
## Create
[Section titled “Create”](#create)
`POST /api/v2/{type}` creates an element and returns `201` with the full element.
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/location" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-d '{ "name": "The Time Tombs", "description": "Structures on Hyperion that move backward through time." }'
```
* You may supply an `id`: any RFC 4122 UUID is accepted, and UUIDv7 is recommended. Omit it and the server mints a UUIDv7.
* An `id` that already exists is `409` [`id_conflict`](/api/errors/#id_conflict). Element ids are unique across all worlds, so the message says which case it is: the id exists in this world (use `PUT` to upsert), or in another world (mint a new id).
## Upsert
[Section titled “Upsert”](#upsert)
`PUT /api/v2/{type}/{id}` creates the element if absent and **replaces it whole** if present: fields left out are reset to empty. It returns `201` when it created and `200` when it replaced. The path decides the id; an `id` in the body is dropped. An id held by an element in another world is `409` [`id_conflict`](/api/errors/#id_conflict).
## Partial Update
[Section titled “Partial Update”](#partial-update)
`PATCH /api/v2/{type}/{id}` changes only the fields sent and returns `200` with the full element.
* Omitted fields are left untouched.
* **Arrays replace.** A `PATCH` to `friends` sets the whole list; to add or remove single ids, use the [link operations route](/docs/development/api/links#link-operations).
* Extension fields merge by key.
* To clear a field, send its empty shape:
| Kind | Clear with |
| ----------- | ------------------------------- |
| Text | `""` or `null` (stored as `""`) |
| Number | `null` |
| Single link | `null` |
| Multi link | `[]` |
## Delete
[Section titled “Delete”](#delete)
`DELETE /api/v2/{type}/{id}` returns `204`. It is idempotent: deleting an element that is already gone also returns `204`. Deleting an element removes its id from every other element’s links, and the change feed records the delete as a [tombstone](/docs/development/api/changes).
## Field Rules
[Section titled “Field Rules”](#field-rules)
* **`name`** is required on `POST` and `PUT`: the key must be present, and an empty string is accepted.
* **Unknown fields** are a `422` naming the field. A misspelled field fails loudly instead of vanishing.
* **Extension fields** under the namespaces `atlas_*`, `shadow_*` and `x_*` are accepted, stored as written and returned verbatim, up to 65,536 bytes of extensions per element (counted as compact UTF-8 JSON). More is a `422` with `param` `extensions`.
* **Server fields** `type`, `created_at`, `updated_at` and `change_seq` appear on reads and are a `422` on writes. Strip them before sending a read body back.
* **Never write back a body read with `?expand=`.** Its links hold stub objects instead of ids, and a link field takes only ids. Read without `expand` when you mean to send the body back.
* **`created_by`** and **`world`** in a body are ignored, whatever their value.
* **Text length**: `name` holds up to 255 characters, `supertype` and `subtype` 128, `image_url` 1024. Longer is a `422`.
* **Values are coerced where they can be**: an integer field accepts `"7"` as 7, truncates `7.9` to 7, and reads `true` as 1; a value that cannot become an integer is a `422`. Text fields turn a number into its digits. Send the types the schema names.
## Idempotency
[Section titled “Idempotency”](#idempotency)
`POST /api/v2/{type}` and `POST /api/v2/bulk` accept an `Idempotency-Key` header (up to 200 characters). The first successful response is stored for 24 hours under that key:
* An identical replay returns the stored response, with an `Idempotent-Replay: true` header, and does not write again.
* The same key with a different body is `409` [`idempotency_error`](/api/errors/#idempotency_error).
* Only successful (`2xx`) responses are stored; errors never are. A `/bulk` answer is a `200` even when items failed, so it is stored, item errors included, and a replay returns the same errors.
The rule for retries:
* **The answer was lost** (a timeout, a dropped connection): retry with the **same** `Idempotency-Key`. A stored `2xx` replays; after an error, the request runs again.
* **A `/bulk` answer had `errors: true`**: fix the failed items and send them with a **new** key. Under the same key, an identical body replays the same errors, and a changed body is a `409` [`idempotency_error`](/api/errors/#idempotency_error).
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/character" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-3b7d-4e0a-9c55-2f8d1b4a7e10" \
-d '{ "id": "0199a1c2-7d3e-7f00-8a11-3c5e7b9d2f40", "name": "The Consul" }'
```
The store suppresses replays on a best-effort basis; it is not a ledger. Minting the `id` on the client keeps a retry safe even without the header: two concurrent creates without client ids can make two elements.
## Bulk
[Section titled “Bulk”](#bulk)
`POST /api/v2/bulk` creates and upserts up to 1000 elements of mixed categories in one call.
```json
{
"items": [
{ "type": "institution", "element": { "id": "0199a1c2-…", "name": "The Hegemony" } },
{ "type": "character", "element": { "id": "0199a1c3-…", "name": "The Consul", "institutions": ["0199a1c2-…"] } }
],
"atomic": false
}
```
```json
{
"errors": false,
"items": [
{ "status": 201, "id": "0199a1c2-…", "created_at": "…", "updated_at": "…" },
{ "status": 201, "id": "0199a1c3-…", "created_at": "…", "updated_at": "…" }
]
}
```
* An item whose `element` carries an `id` upserts that id; one without an `id` creates. An `op` field on an item is optional and ignored.
* The answer is HTTP `200` once the batch is read. Results are per item, in request order, and each `status` is what a single write would have returned (`201`, `200`, or an error status).
* **Partial success is the default**: one bad item does not stop the rest. Send `"atomic": true` for all or nothing. In atomic mode, if `errors` is `true` **nothing was written**, even though the items that would have succeeded show `201` or `200`.
* A failed item carries the standard [error envelope](/api/errors/) under `error`; the top-level `errors` is `true` if any item failed. The codes seen per item are listed under [Bulk Errors](/api/errors/#bulk-errors).
* Links are checked against the world **plus the batch’s surviving items**, in any order, so a batch may reference its own items without sorting. All checks run before any write, and an item that fails also fails the items linking to it.
* Each success echoes the server’s `created_at` and `updated_at`, so a sync client can set its baseline from the bulk response alone.
* A malformed request (not valid JSON, `items` not an array, over 1000 items) or an auth failure answers with its own status and the error envelope, not `200`.
## The World
[Section titled “The World”](#the-world)
`PATCH /api/v2/world` updates the world’s own fields. It takes a write key and PIN and is **owner only**: a member’s key, even a co-builder’s, gets `403` [`owner_only`](/api/errors/#owner_only).
| Field | Type |
| -------------------------------------------------------- | ------------------------------------------------------------------------- |
| `name`, `description`, `image_url`, `time_basic_unit` | String |
| `time_format_names`, `time_format_equivalents` | List of strings |
| `time_range_min`, `time_range_max`, `time_range_current` | Integer or `null` |
| `owner_character` | A Character id in this world (the Character that is the owner), or `null` |
```bash
curl -s -X PATCH "https://www.onlyworlds.com/api/v2/world" \
-H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \
-d '{ "time_basic_unit": "Year", "time_range_current": 2732 }'
```
* Unknown fields are a `422`, and the whole patch is refused before anything is written.
* The world’s `name` cannot be empty, unlike an element’s.
* `public_read` is set in the [account portal](https://www.onlyworlds.com/account/) and the PIN in [account settings](https://www.onlyworlds.com/account/settings), not here.
* Every field sent is applied, so an identical value still moves `updated_at`. Send only real changes.
* World fields do not appear in [`/changes`](/docs/development/api/changes). To follow them, poll `GET /api/v2/world` and compare `updated_at`.
# Games
> Building a game on an OnlyWorlds world: carry it in the build, read it live, or write back into it, from Unity or any other engine.
To a game, a world on OnlyWorlds is content: characters, creatures, places, items, factions and events, with typed links between them. The world stays editable outside the game, on [onlyworlds.com](/docs/tools/onlyworlds-com), in [Atlas](/docs/tools/atlas) or in any tool built on the schema. So writers and designers can shape it without opening the engine, and the game picks up their work.
## Three Ways to Use a World
[Section titled “Three Ways to Use a World”](#three-ways-to-use-a-world)
### Bake It In
[Section titled “Bake It In”](#bake-it-in)
Fetch the world at build time and ship it inside the game. It works offline, needs no key at runtime, and changes only when you rebuild. This fits a world that is written first and then played.
* **Unity**: fill a [cache asset](/docs/development/unity#ship-a-world-with-your-game) in the Editor and reference it from your scripts.
* **Any engine**: read a world folder (one JSON file per element, the format Atlas and the [Python package](/docs/development/python) use), or take a [full export](/docs/development/api/changes#full-export) from the API, and convert it into the engine’s own data.
### Read It Live
[Section titled “Read It Live”](#read-it-live)
The game reads the world from the API while it runs, so a new character or a rewritten place reaches players without a patch. Fetch the world once, then ask the [changes feed](/docs/development/api/changes) for what changed since your cursor, rather than fetching everything again.
A read key (`ow_r_`) is made for this: it reads, cannot write, and needs no PIN. A key inside a shipped game can be extracted, so assume your players can read the whole world. See [Keys and PINs](/docs/getting-started/keys).
What a live read can count on:
* **It’s free**, with no fee and no account tiers.
* **There is no request quota on reads** per key.
* **Under load the server answers `503`** [`server_busy`](/api/errors/#server_busy) with a `Retry-After` header. Wait that long, then retry.
* **Image uploads have a daily cap** per world.
* **There is no uptime guarantee.**
So a game that must never break should bake its world in, and read live only the content that may change.
### Write Back
[Section titled “Write Back”](#write-back)
The game writes its outcomes into the world: who died, which town burned, what was found. The world becomes the record of play, and every other tool on it sees the result. Writing needs a write key and the PIN, so keep it on a server or in a tool you control, not in a game you ship to players.
Four rules keep a write from damaging work done elsewhere:
* **Send only the fields that changed.** A `PATCH` replaces every field it carries, so sending back an element fetched earlier undoes edits made since.
* **Change link lists with editLinks**, which adds and removes ids on the server. A `PATCH` with a link list replaces the whole list. See [Links](/docs/development/api/links).
* **Check every bulk response.** A bulk write answers HTTP 200 even when some items failed. See [Writes](/docs/development/api/writes).
* **Keep game-only state in extension fields** (`x_yourgame_*`), which the server stores and returns verbatim, up to 64 KB per element. A well-behaved client carries other tools’ extensions through untouched. See [Extension Fields](/docs/schema/fields#extension-fields).
## Engines
[Section titled “Engines”](#engines)
| Engine | The path today |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Unity** | The [Unity SDK](/docs/development/unity): typed C# models, a v2 client, a world cache that ships as an asset, and a world browser in the Editor. |
| **Browser games** (JavaScript, TypeScript) | The [TypeScript SDK](/docs/development/typescript). |
| **Godot, Unreal and other engines** | The [REST API](/docs/development/api-reference) directly: HTTPS and JSON, with the key in a header (the call below). Generate a client from the [OpenAPI document](https://www.onlyworlds.com/api/v2/openapi.json), and types for the 22 element types from [schema-dist](https://github.com/OnlyWorlds/schema-dist) (its decoder, the walk, is Python today). |
| **Build pipelines and tools** | The [Python package](/docs/development/python) reads and writes world folders and talks to the API. |
Every path ends in the same call. This one reads three characters of Moppetopia, a public sample world, with its demo key, the same first run the SDKs start with:
```bash
curl -H "API-Key: 0000000001" \
"https://www.onlyworlds.com/api/v2/character/?fields=id,name&limit=3"
```
Unity is the first engine with an SDK of its own. Until another engine has one, its path is the API.
## Game Content in the Schema
[Section titled “Game Content in the Schema”](#game-content-in-the-schema)
The 22 element types cover most of what a game holds. A starting point:
| In your game | Element type |
| ------------------------------------------------------------- | ----------------------------------------------------------- |
| Player characters, NPCs | Character |
| Races and peoples | Species |
| Monster and animal types (goblin, frost wyrm, the stat block) | Species |
| Individual monsters (the goblin chief, a named dragon) | Creature, linked to its Species |
| Items, gear, loot | Object |
| Skills, spells, actions | Ability |
| Passive perks, qualities | Trait |
| Guilds, kingdoms, orders | Institution (organized) or Collective (no formal structure) |
| Magic systems, currencies, crafting rules | Construct |
| Rules of a faction or realm | Law |
| Ranks and offices | Title |
| Languages, including those spoken in dialogue | Language |
| Quests and storylines | Narrative |
| Battles and happenings | Event |
| Weather, curses, magic storms | Phenomenon |
| Reputation, rivalries, alliances | Relation |
| Towns, dungeons, levels | Location |
| Territories and regions | Zone |
| Maps and what is placed on them | Map, Pin, Marker |
The line between Character and Creature is agency, not species: an intelligent monster with goals of its own can be a Character. `supertype` and `subtype` hold your game’s own categories (a Creature with supertype `Boss`), so you don’t need new element types for them. Where two types are close, [Conventions](/docs/schema/conventions) draws the line between them. Each type’s fields are listed under [Schema](/docs/schema/).
## On OnlyWorlds Today
[Section titled “On OnlyWorlds Today”](#on-onlyworlds-today)
[Tactical Tangle](https://tangle.onlyworlds.com) is a hoplite battle game in which your OnlyWorlds characters fight in the ranks of a phalanx. Everything else built on OnlyWorlds is listed under [Tools](/docs/tools/).
# LLM Guide
> A single text file that teaches a chat assistant OnlyWorlds, so it can help you build a tool on the standard.
The LLM guide is one plain-text file, written for AI assistants, that teaches the OnlyWorlds standard, the 22 element categories, the API and the TypeScript SDK. Paste it into a chat and the assistant can help you plan and build your own tool on OnlyWorlds.
**The file**:
The guide is for people building their own tools. Someone who wants to build a world can go straight to [Atlas](/docs/tools/atlas), and someone working in Claude Code is better served by the [toolkit](/docs/development/toolkit), which the guide itself points to.
## Using It in a Chat
[Section titled “Using It in a Chat”](#using-it-in-a-chat)
1. Open the file and copy all of it, or download it.
2. Paste it into a new chat (ChatGPT, Claude, or a browser builder such as Lovable, Replit, v0.dev or ChatGPT Canvas).
3. Describe the tool you want. The guide tells the assistant to ask what problem it solves, who uses it, its one core interaction, and which element categories matter, then to write a short spec with you before writing code.
For full field definitions beyond the guide, point the assistant at the [schema](/docs/schema/) or at `FIELD_SCHEMA` in the [SDK](/docs/development/typescript).
## What It Covers
[Section titled “What It Covers”](#what-it-covers)
| Part | Contents |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1. How to help the user | Gauging experience (no coding, some coding, advanced), planning a tool before building it with a short spec, choosing between browser builders and a local setup, pointing Claude Code users to the toolkit, and local setup (Node.js, Git, an editor). |
| 2. OnlyWorlds essentials | What OnlyWorlds is (an open standard; a world as a folder of JSON files or a hosted world with a REST API), the 22 categories in six groups with one line each, how common concepts map onto them, and `x_` extension fields for custom data. |
| 3. Credentials and first call | Minting a key (`ow_w_`, `ow_r_`, `ow_a_`, legacy), where the PIN is set and when it is sent, the demo keys, keeping credentials out of chats, commits and browser bundles, and a first SDK call. |
| 4. SDK and API reference | Plain HTTP (the base URL, the `API-Key` and `API-Pin` headers, the error envelope); reads and cursor paging, filters, `expand` and `fields`; create, patch, upsert and delete; link fields and `editLinks`; bulk upload with client-minted ids and idempotency keys; the change feed; error codes; UI helpers (icons, colours, field groups, `FIELD_SCHEMA`); reading a world folder with no account. |
| 5. Deployment | Building, the static hosts the API accepts browser calls from without setup, deploying to Cloudflare Pages, and why a public site uses a read key. |
| 6. Resources | Links to Atlas, the developer start, the API reference, the SDK, the standard, schema-dist, the toolkit, the MCP server, the tools and the community. |
The file ends with its own changelog.
## Related
[Section titled “Related”](#related)
* [MCP server](/docs/development/mcp): connect an assistant to a world directly instead of pasting the guide.
* [Toolkit](/docs/development/toolkit): the same knowledge as Claude Code skills, readable by other agents as markdown.
* [AI agents](/docs/development/ai): the four ways an AI works with a world.
# MCP Server
> The hosted OnlyWorlds MCP server, how to connect a client to it, and the tools it offers.
OnlyWorlds runs a hosted [Model Context Protocol](https://modelcontextprotocol.io) server. Any MCP client can read and write your worlds through it, with nothing to install.
**Server URL**: `https://www.onlyworlds.com/mcp`
* **Transport**: streamable HTTP. Point an MCP client at the URL; there is no package to install.
* Opening the URL in a browser shows a plain info page. MCP clients POST JSON-RPC to the same URL.
* The old npm client (`@onlyworlds/mcp-client`) and the `/mcp/messages/` endpoint are retired. Use the hosted server above.
The server runs on the same schema registry and service layer as the [World API](/docs/development/api-reference): an element read or written through MCP has the same body, validation and permissions as through `/api/v2/`. The tools are shaped for assistants, so paging, filters and errors differ: see [How It Differs From REST](#how-it-differs-from-rest).
## Connect From Claude Code
[Section titled “Connect From Claude Code”](#connect-from-claude-code)
The schema tools need no account and no key:
```bash
claude mcp add --transport http onlyworlds https://www.onlyworlds.com/mcp
```
For your own world, the same command with your credentials as headers:
```bash
claude mcp add --transport http onlyworlds https://www.onlyworlds.com/mcp --header "API-Key: " --header "API-Pin: "
```
Each command is a single line: paste it whole. A backslash-continued form breaks in PowerShell.
## Connect Other Clients
[Section titled “Connect Other Clients”](#connect-other-clients)
Any MCP client that can send HTTP headers uses the same URL and the same two headers. Claude Desktop and the Anthropic API’s MCP connector work this way.
Codex reads the server from `~/.codex/config.toml`, taking the two header values from environment variables:
```toml
[mcp_servers.onlyworlds]
url = "https://www.onlyworlds.com/mcp"
env_http_headers = { "API-Key" = "OW_API_KEY", "API-Pin" = "OW_API_PIN" }
```
`OW_API_KEY` and `OW_API_PIN` are the names an [agent seat’s](/docs/development/agents) join response uses. Any variable names work, as long as the config and the environment agree.
## Credentials
[Section titled “Credentials”](#credentials)
Credentials travel as headers on every request, exactly as on the REST API.
| Header | Value |
| --------- | ---------------------------------------------------- |
| `API-Key` | A world key. The key scopes the server to one world. |
| `API-Pin` | The PIN, required for writes. |
* Keys are minted on your world’s page in the [account portal](https://www.onlyworlds.com/account/). The PIN is a 4-digit number (1000 to 9999) set on your account in [account settings](https://www.onlyworlds.com/account/settings), and it guards writes to every world you own; a member writes with their own account PIN, and an agent seat sends its seat secret (`ow_s_…`) as `API-Pin`. See [Keys and PINs](/docs/getting-started/keys).
* A read-only `ow_r_` key needs no PIN. A prefixed write key (`ow_w_`) reads without the PIN too; only a legacy 10-digit key reading a private world must send it.
* The client saves both headers in its configuration as written. For read-only use, connect with an `ow_r_` key and no PIN.
## Tools
[Section titled “Tools”](#tools)
The tools fall into three groups by what they need.
### Schema: No Key
[Section titled “Schema: No Key”](#schema-no-key)
| Tool | What it does |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_element_types` | List all 22 element categories with a one-line shape summary of each. |
| `get_element_schema` | Return the fields of one category, grouped by kind (text, integer, single link, multi link, generic), with each link field’s target category. |
| `search_schema` | Search every category’s fields for a substring, such as “which categories have a `location` field?”. |
### Read: A Key
[Section titled “Read: A Key”](#read-a-key)
| Tool | What it does |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_elements` | List elements of one category in the key’s world, newest first. Filters by `name_contains` and `supertype`; pages with `limit` (default 100, up to 1000) and `offset`. |
| `get_element` | Fetch one element by category and UUID, in the same shape as `GET /api/v2/{type}/{id}`. |
| `search_elements` | Search elements by name across all 22 categories in the world, up to 50 matches per category. |
| `get_changes` | Return the world’s delta feed (upserts and deletes since a cursor), paged: 25 entries per call by default, `limit` up to 1000. A guest key gets only what it can see and no deletes; when its view changes, the tool says to pull again from the start and replace the local copy. Mirrors [`GET /api/v2/changes`](/docs/development/api/changes). |
### Write: A Write Key and PIN
[Section titled “Write: A Write Key and PIN”](#write-a-write-key-and-pin)
| Tool | What it does |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_element` | Create one new element of a given category. Supply your own `id` (a UUID) or let the server mint one. |
| `update_element` | Update an existing element by category and id. A server-side read-merge: it changes only the fields you pass and leaves the rest intact. A multi-link field you pass replaces that field’s whole array. |
| `edit_links` | Add and/or remove links on one multi-link field, leaving the field’s other links untouched. |
| `bulk_apply` | Create and/or update up to 1000 elements across any categories in one call. An item with an `id` updates that element (creating it if absent); items can link to each other. With `atomic` true, any failure rolls the whole batch back. |
| `get_image_upload_ticket` | Return a single-use ticket for one image upload: `ticket`, `upload_url`, `max_bytes`, `exp`. The image never passes through the tool: the agent uploads the bytes itself to `upload_url` with the ticket, then calls `update_element` with the returned `url` as `image_url`. See [Images](/docs/development/api/images). |
Note
There is no delete tool, by design. The MCP server creates and edits; deletion stays in the REST API and the portal, so an assistant cannot remove elements on its own. `bulk_apply` never removes an element either.
## How It Differs From REST
[Section titled “How It Differs From REST”](#how-it-differs-from-rest)
| | MCP tools | REST (`/api/v2/`) |
| ------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| Listing order | `list_elements`: newest created first | Change order, the order the cursor walks |
| Paging | `limit` and `offset`; the answer is `{data, limit, offset, has_more}` | `limit` and `cursor`; the answer is `{data, has_more, next_cursor}` |
| Name filter | `name_contains` (case-insensitive substring) | `name__icontains`, or `name` for an exact match. `offset` and `name_contains` are a `422` here |
| Other filters | `supertype` | `supertype`, `subtype`, and `characters` on six categories ([Reads](/docs/development/api/reads#filters)) |
| Search across categories | `search_elements` | None: filter one category at a time |
| Change feed | `get_changes`: `since_cursor`, 25 entries per page by default | `GET /changes`: `since`, 500 per page by default |
| Errors | A tool error carrying the human message, without `code` or the other envelope fields | The [error envelope](/api/errors/) |
| Retries | No `Idempotency-Key` | `Idempotency-Key` on `POST` and `/bulk` ([Writes](/docs/development/api/writes#idempotency)) |
| Delete | No tool | `DELETE /api/v2/{type}/{id}` |
## See Also
[Section titled “See Also”](#see-also)
* [World API](/docs/development/api-reference): the same data over REST, with the interactive reference at [onlyworlds.com/api/docs](https://www.onlyworlds.com/api/docs).
* [Agent seats](/docs/development/agents): give an agent its own key and Character in a world, then connect it here.
* [Toolkit](/docs/development/toolkit): Claude Code skills that work on a world folder or an account world.
# SDKs and Clients
> Every way to call OnlyWorlds from code, by language and by where it runs.
Every client below speaks the same REST API v2 and the same 22 element types. Pick by language, or by where your code runs.
| Client | For | Install | State |
| ---------------------------------------------- | ----------------------------------- | ------------------------------------------------------------ | -------------------------------- |
| [TypeScript SDK](/docs/development/typescript) | web apps, Node tools, browser games | `npm install @onlyworlds/sdk` | published on npm |
| [Python package](/docs/development/python) | scripts, pipelines, world folders | `pip install "git+https://github.com/OnlyWorlds/python-sdk"` | pre-release, not on PyPI yet |
| [Unity SDK](/docs/development/unity) | Unity games and tools | Package Manager, by git URL | public, interface not yet stable |
| [MCP server](/docs/development/mcp) | AI assistants and agents | nothing to install: `https://www.onlyworlds.com/mcp` | live |
| Plain REST | any other language or engine | none | live |
The TypeScript, Python and Unity clients generate their types from [schema-dist](https://github.com/OnlyWorlds/schema-dist), the published copy of the schema. For other engines, see [Games](/docs/development/games).
## Plain REST
[Section titled “Plain REST”](#plain-rest)
The API needs no SDK. Send the key (and, for writes, the PIN) as headers:
```python
import requests
headers = {"API-Key": "your-key", "API-Pin": "your-pin"}
r = requests.get("https://www.onlyworlds.com/api/v2/character/", headers=headers)
characters = r.json()["data"]
```
List responses come back in a `{data, has_more, next_cursor}` envelope, so paginate on `next_cursor`. To generate a client in another language, start from the OpenAPI document at `https://www.onlyworlds.com/api/v2/openapi.json`; the [interactive reference](https://www.onlyworlds.com/api/docs) is built from the same document.
## Retired
[Section titled “Retired”](#retired)
The `@onlyworlds/mcp-client` npm package is retired: the MCP server is hosted now, with nothing to install.
# Python Package
> The onlyworlds Python package: the world folder format and a client for the v2 API. A pre-release, installed from GitHub.
Pre-release
The package is not on PyPI yet. Install it from GitHub, and expect the interface to change before the first release.
`onlyworlds` is the Python package for OnlyWorlds. It reads and writes the world folder format, pushes a folder’s changes to a world, and has a client for the REST API v2. It needs Python 3.12 or later and has no runtime dependencies.
The 22 element types and the kind of every field are generated from [schema-dist](https://github.com/OnlyWorlds/schema-dist), never listed by hand. The package’s version is its own and does not track the schema’s.
## Install
[Section titled “Install”](#install)
```bash
pip install "git+https://github.com/OnlyWorlds/python-sdk"
```
The `onlyworlds` package on TestPyPI is an older, unrelated package for the v1 API.
## Read a world
[Section titled “Read a world”](#read-a-world)
```python
from onlyworlds import Client
client = Client("ow_r_...") # a read key needs no PIN
for character in client.iter_elements("character"):
print(character["name"])
```
A write key also takes a PIN: `Client(key, pin)`. The PIN is a 4-digit number (1000 to 9999) set on your account in account settings, and it guards writes to every world you own. A member writes with their own account PIN, and an agent seat sends its seat secret (`ow_s_…`) as the PIN.
Elements are plain dicts. Two demo keys read public sample worlds: `0000000000` (Hyperion) and `0000000001` (Moppetopia).
## Write
[Section titled “Write”](#write)
```python
writer = Client("ow_w_...", "1234") # a write key and its PIN
peak = writer.create("location", {"name": "Dragon Peak"})
writer.patch("location", peak["id"], {"supertype": "Mountain"})
# add or remove links on a multi-link field without replacing the list
writer.edit_links("location", peak["id"], "founders", add=[character_id])
```
`create` gives an element without an `id` one, and always sends an `Idempotency-Key`. So retrying a create after a lost answer is safe: it replays the stored result instead of making a second element. `patch` replaces every field it sends, and a list replaces the whole list. Use `edit_links` to add or remove links. `bulk` takes up to about 1,000 `{"type", "element"}` items: it succeeds partly by default (check each slot’s `status`), or all or nothing with `atomic=True`.
## Follow changes
[Section titled “Follow changes”](#follow-changes)
```python
walk = client.walk_changes(saved_cursor) # None for everything
for op in walk:
... # op["op"] is "upsert" or "delete"
saved_cursor = walk.cursor # opaque: persist it, never parse it
```
If the feed refuses a cursor (`is_resync_required` on the error), walk again from the start and replace the local copy. See [Sync with Changes](/docs/development/api/changes).
## What it covers
[Section titled “What it covers”](#what-it-covers)
* **World folders**: `read_folder` and `write_folder` implement the folder format and pass its shared conformance fixture. The reader never changes a folder.
* **Push**: `plan_push` and `push` compare a folder with a baseline and PATCH only the fields that changed. A rerun skips what already landed.
* **Client**: `get_world`, `patch_world`, `list_page`, `iter_elements`, `get`, `create`, `upsert`, `patch`, `delete`, `edit_links`, `bulk`, `changes` and `walk_changes`. `create` and `bulk` always send an `Idempotency-Key`, so a retry after a lost answer is safe: it never creates an element twice.
* **Errors**: `ApiError` carries the envelope’s `code`, `param` and `doc_url`, with flags such as `is_id_conflict`, `is_not_author`, `is_resync_required` and `is_validation_error`.
* **Export**: `export_world`.
Not yet: typed element models, the account routes and the snapshot writer.
## Links
[Section titled “Links”](#links)
* [Source and README](https://github.com/OnlyWorlds/python-sdk)
* [Interactive API reference](https://www.onlyworlds.com/api/docs)
# Toolkit
> The OnlyWorlds toolkit, a Claude Code plugin of skills for structuring, growing, playing in and building on worlds.
The toolkit is a Claude Code plugin: a set of skills, an orchestration agent and a knowledge base for working with worlds. Its skills follow the life of a world: structure it, grow it, play in it and keep it true, build on it. Any AI that reads markdown can use its files; it is built for Claude Code.
**Repository**: [github.com/OnlyWorlds/toolkit](https://github.com/OnlyWorlds/toolkit)
## Install
[Section titled “Install”](#install)
In Claude Code:
```bash
/plugin marketplace add OnlyWorlds/toolkit
/plugin install toolkit@onlyworlds
```
Restart Claude Code to load it. Or load it straight from a clone, with no restart:
```bash
git clone https://github.com/OnlyWorlds/toolkit
claude --plugin-dir ./toolkit
```
### Other AIs
[Section titled “Other AIs”](#other-ais)
Point the assistant at the knowledge files, in this order:
1. [principles.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/principles.md): the shared rules every skill follows
2. [world-folder.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/world-folder.md): the world as files, and keeping its history
3. [world-memory.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/world-memory.md): loading a world into AI sessions and writing each session back
4. [onlyworlds-core.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/onlyworlds-core.md)
5. [modeling-patterns.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/modeling-patterns.md)
6. [schema-reference.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/schema-reference.md)
7. [ai-builders.md](https://raw.githubusercontent.com/OnlyWorlds/toolkit/main/knowledge/ai-builders.md)
The skill files themselves sit in the repository’s `skills/` folder, one folder per skill, and read as plain markdown.
## Skills
[Section titled “Skills”](#skills)
| Stage | Skill | What it does |
| --------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| Start | `onlyworlds-start` | The entry point: works out your situation and routes you to the right skill. |
| Structure it | `parsing` | Turns text, files or folders (notes, novels, campaign docs, wikis) into a structured world: JSON, or a world folder Atlas opens. |
| | `modeling` | Design help for complex systems (magic, politics, economies, tech trees) as linked elements. |
| | `schema` | Field reference across all 22 categories: look up fields, validate structure, tell similar categories apart. |
| Grow it | `survey` | Reads a world and writes a creative brief of its themes, tensions and structure. |
| | `link` | Finds missing connections, enriches links and resolves orphaned elements. |
| Play in it and keep it true | `context` | Designs how the right part of a world gets loaded into each AI session: fresh, scoped, sourced. |
| | `resolve` | Designs how a finished session (notes, recap, transcript) gets written back into the world, so canon holds. |
| Build on it | `api` | Reads, creates, updates and deletes elements through natural language, handling auth, endpoints and field mapping. |
| | `dev` | SDK integration, project scaffolding, credentials and deployment. |
| | `council` | Browse and draft schema governance motions when the 22 categories don’t fit. |
| | `project-setup` | Connects a project to an OnlyWorlds account world: credentials and a local cache. Runs when another skill needs it. |
The orchestration agent, `ow-agent`, chains several skills for multi-step jobs, such as parsing a whole corpus and then linking and surveying it. The knowledge base covers the schema reference, the category descriptions, and decision trees for ambiguous modeling choices.
## A World Folder or an Account World
[Section titled “A World Folder or an Account World”](#a-world-folder-or-an-account-world)
Most of the toolkit runs on files alone, with no account:
* `parsing`, `modeling` and `schema` work fully standalone.
* `survey` and `link` read a world folder straight off disk: an Atlas world, converter output, or anything in the [world folder shape](https://github.com/OnlyWorlds/toolkit/blob/main/knowledge/world-folder.md).
* `parsing` can write that folder shape directly, so your notes become a world Atlas opens.
* `context` and `resolve` work with whatever holds the world: a folder, your own notes, or an OnlyWorlds world.
With an OnlyWorlds account, the same skills work on an account world over the [API](/docs/development/api-reference). `project-setup` connects the project (a `.env` you fill with your key and PIN as `ONLYWORLDS_API_KEY` and `ONLYWORLDS_API_PIN`, kept out of git), and `api` and `dev` build on it. To connect an agent to a world directly, see the [MCP server](/docs/development/mcp).
## Example Requests
[Section titled “Example Requests”](#example-requests)
```text
"Help me organize my worldbuilding notes"
"Turn my notes into a world folder"
"How should I model a magic system?"
"What fields does Character have?"
"Find orphaned elements in my world"
"My AI keeps forgetting my campaign's canon"
"Turn last session's notes into world updates"
"I'm building a game with world data"
```
# TypeScript SDK
> The @onlyworlds/sdk package, a typed client for the v2 API and the schema's constants, generated from schema-dist.
`@onlyworlds/sdk` is the typed client for the OnlyWorlds REST API v2. It also exports the schema’s constants (the 22 element types, their icons, colour families and field schema), generated from [schema-dist](https://github.com/OnlyWorlds/schema-dist) at a pinned, hash-verified tag. The generated files name that tag in their header.
The 4.x line speaks v2 only and is ESM-only (Node 18 or later). Tools on the v1 API dialect (`OnlyWorldsClient`) or on CommonJS `require()` stay on the 3.x line, which is still published.
## Install
[Section titled “Install”](#install)
```bash
npm install @onlyworlds/sdk
```
## Read a world
[Section titled “Read a world”](#read-a-world)
```typescript
import { OwV2Client } from '@onlyworlds/sdk';
const client = new OwV2Client({ apiKey: 'ow_r_your_key' });
const page = await client.list('character'); // { data, has_more, next_cursor }
for await (const character of client.listAll('character')) {
console.log(character.name);
}
```
A read-only key (`ow_r_`) needs no PIN. Two demo keys read public sample worlds: `0000000000` (Hyperion) and `0000000001` (Moppetopia). See [Keys and PINs](/docs/getting-started/keys) for the key types.
## Write
[Section titled “Write”](#write)
```typescript
const writer = new OwV2Client({ apiKey: 'ow_w_your_key', apiPin: '1234' });
const peak = await writer.create('location', { name: 'Dragon Peak' });
const dragon = await writer.create('creature', { name: 'Vorrath', location: peak.id });
await writer.patch('location', peak.id, { supertype: 'Mountain' });
const breath = await writer.create('ability', { name: 'Ember Breath' });
await writer.editLinks('creature', dragon.id, 'abilities', { add: [breath.id], remove: [] });
```
The v2 rules the client follows:
* A link field has one bare name in both directions (`location`, `abilities`). The `_id` and `_ids` suffixes belong to v1.
* Never send a `world` field: the key decides the world, and the client strips the field.
* `name` is the one required field. `''` and `null` are accepted and stored as `''`.
* An `id` is minted on the client when you leave it out, so a retried create stays idempotent. (A plain REST request without an `id` gets one from the server instead.)
* `patch` replaces every field it sends, and an array replaces the whole list. To add or remove links without replacing them, use `editLinks`.
## Bulk writes
[Section titled “Bulk writes”](#bulk-writes)
A bulk write succeeds partly by default: HTTP 200 with a status per slot. Check `errors` every time.
```typescript
const res = await writer.bulk(
[
{ type: 'character', element: { name: 'A' } },
{ type: 'event', element: { name: 'B' } },
],
{ idempotencyKey: crypto.randomUUID() }, // keep it for a retry if the answer is lost
);
if (res.errors) {
for (const slot of res.items.filter((s) => s.status >= 400)) {
console.warn(slot.error?.code, slot.error?.message, slot.error?.doc_url);
}
}
```
* `{ atomic: true }` makes the batch all or nothing. After a failed atomic batch nothing was written, but the slots that would have succeeded still report 201: do not record those ids as created.
* The server stores every 2xx answer under its idempotency key, and never an error. So when an answer is lost, retry with the **same** key: a stored answer replays, and an error runs again.
* A bulk answer with item errors is still a 200, so it is stored too. Fix the failed items and send them with a **new** key, because the same key replays the same errors.
* `res.wasReplay` is true when the server answered from that store.
## Images
[Section titled “Images”](#images)
```typescript
const image = await writer.uploadImage(file); // a Blob, File, ArrayBuffer or Uint8Array
await writer.patch('character', id, { image_url: image.url });
```
This makes two requests: a single-use ticket from the API, then the bytes straight to the media edge. The API never sees the bytes, and the edge never sees your key. Accepted formats are webp, png, jpeg and avif, read from the bytes (never SVG or gif). Each ticket counts toward the world’s daily limit and the account’s image storage. For a progress bar, call `createMediaTicket()` and POST the bytes to its `upload_url` yourself, with `Authorization: Bearer `.
## Follow changes
[Section titled “Follow changes”](#follow-changes)
```typescript
let cursor; // opaque and never expires: persist it
for await (const change of client.changesAll(cursor)) {
// changes arrive in (change_seq, id) order; apply them in order
}
```
Edits to the world itself (its name, calendar, `public_read`) do not enter the change feed. Poll `client.getWorld()` and compare `updated_at` for those. See [Sync with Changes](/docs/development/api/changes).
## Errors
[Section titled “Errors”](#errors)
Every non-2xx answer throws `OwApiError` with the platform’s error envelope: `.status`, `.type`, `.code`, `.param` (the field that failed) and `.docUrl`, which links to the code on [the errors page](/api/errors). Show `docUrl` to your users. Transport failures throw `OwNetworkError`. `err.isValidationError` covers the common case.
## Schema constants
[Section titled “Schema constants”](#schema-constants)
```typescript
import {
ELEMENT_TYPES, // the 22 type slugs, and the ElementType union
ELEMENT_ICONS, // the Material Symbols icon for each type
ELEMENT_LABELS, // plural display labels
ELEMENT_SECTIONS, // the field groups and their display order
FIELD_SCHEMA, // type and target of every field
elementColor, // the colour of a type's family
} from '@onlyworlds/sdk';
elementColor('character', 'dark');
```
Colour marks a type’s family and the icon marks the type. Always pair a colour with its icon and label, because colour alone is not accessible. The package also ships `SCHEMA.md` (every field and its meaning, generated) and `AGENTS.md` for AI agents working in a codebase that uses it.
## SDK or MCP?
[Section titled “SDK or MCP?”](#sdk-or-mcp)
Use the SDK for known operations in code: reads, writes, sync, bulk. For an AI exploring a world from a chat or an agent, use the [MCP server](/docs/development/mcp) at `https://www.onlyworlds.com/mcp`. It takes the same `API-Key` and `API-Pin` headers.
## Links
[Section titled “Links”](#links)
* [npm](https://www.npmjs.com/package/@onlyworlds/sdk) · [Source](https://github.com/OnlyWorlds/sdk) · [Changelog](https://github.com/OnlyWorlds/sdk/blob/main/CHANGELOG.md) · [Migrating from 3.x to 4.x](https://github.com/OnlyWorlds/sdk/blob/main/docs/migrating-3-to-4.md)
* [Interactive API reference](https://www.onlyworlds.com/api/docs)
## Reference
[Section titled “Reference”](#reference)
Generated from the types of `@onlyworlds/sdk` 4.7.0, the version these docs pin. The full declarations ship in the package (`dist/index.d.ts`), with `SCHEMA.md` and `AGENTS.md` beside them.
### Client options
[Section titled “Client options”](#client-options)
`new OwV2Client(config)` takes:
| Option | Type | Notes |
| ------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey` (required) | `string` | The key: an ow_w\_ (write) or ow_r\_ (read) world key, an ow_a\_ account token (sent as a Bearer token, for the account routes), or a 10-digit legacy key. |
| `apiPin` | `string` | The PIN, needed for writes when the world has one, and for legacy-key reads of private worlds. |
| `baseUrl` | `string` | The API’s base URL, default . |
| `pageSize` | `number` | Page size for element lists, default 100 (the server’s default; at most 1000). |
| `changesPageSize` | `number` | Page size for /changes pulls, default 100. |
| `fetch` | `typeof globalThis.fetch` | A fetch implementation to use instead of globalThis.fetch (for tests and other runtimes). |
### Methods
[Section titled “Methods”](#methods)
| Method | Route | What it does | Returns |
| ---------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `health()` | `GET /health` | unauthenticated liveness pulse. | `Promise` |
| `getWorld()` | `GET /world` | world meta (name, calendar/time fields, public_read). | `Promise` |
| `patchWorld(partial)` | `PATCH /world` | partial world-meta update. | `Promise` |
| `list(type, params?)` | `GET /{type}/` | one cursor page. | `Promise` |
| `listAll(type, params?)` | | Cursor-walk every page of a type. | `AsyncGenerator` |
| `get(type, id, opts?)` | `GET /{type}/{id}/` | optional one-level stub expansion / sparse fields. | `Promise` |
| `create(type, element, opts?)` | `POST /{type}/` | create one element, minting a UUIDv7 id on the client when you leave id out. | `Promise` |
| `upsert(type, id, element)` | `PUT /{type}/{id}/` | The local-first write primitive. | `Promise` |
| `patch(type, id, partial)` | `PATCH /{type}/{id}/` | DESTRUCTIVE on sent fields: arrays replace wholesale, omitted fields stay untouched. | `Promise` |
| `delete(type, id)` | `DELETE /{type}/{id}/` | idempotent (204 on absent). | `Promise` |
| `editLinks(type, id, field, edit)` | `POST /{type}/{id}/links/{field} with {add, remove}` | atomic link merge. | `Promise` |
| `bulk(items, opts?)` | `POST /bulk` | up to \~1000 items. | `Promise` |
| `changes(opts?)` | `GET /changes` | one page of the world’s ordered change feed. | `Promise` |
| `changesAll(since?)` | | Walk the feed from `since` (or from zero = full export) to the current tail, yielding ops in order. | `AsyncGenerator` |
| `createMediaTicket()` | `POST /media/ticket` | permission to upload ONE image into this world (a write key and its PIN; no body). | `Promise` |
| `uploadImage(image, opts?)` | | Upload one image and get its permanent public URL: a ticket from the API, then the bytes straight to the media edge (the API never sees them). | `Promise` |
| `request(method, path, opts?)` | | Raw authenticated request against this client’s baseUrl. | `Promise` |
# Unity SDK
> The OnlyWorlds Unity SDK: typed C# models for the 22 element types, a client for the v2 API, and a world cache that lives in your project as an asset.
OnlyWorlds is an open schema for world data: characters, creatures, places, items, factions and events as 22 element types with typed links between them. Worlds are hosted on [onlyworlds.com](https://www.onlyworlds.com) and edited there or in any tool built on the schema. For what that gives a game, see [Games](/docs/development/games).
`com.onlyworlds.sdk` reads and writes those worlds from Unity. It has three parts:
| Part | Assembly | What it is |
| ---------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Bridge** | `OnlyWorlds.Sdk` | Typed models for the 22 element types, a client for the REST API v2, and sync from the changes feed. This is what game code links against. |
| **Cache** | `OnlyWorlds.Sdk` | A world as a `ScriptableObject` asset: inspectable, offline, and kept across domain reloads. Filled from the API or from a world folder on disk. |
| **Viewer** | `OnlyWorlds.Sdk.Editor` | A world browser in the Editor: **Window → OnlyWorlds → World Browser**. |
The models are generated from [schema-dist](https://github.com/OnlyWorlds/schema-dist) at a pinned tag, never written by hand. The source is [OnlyWorlds/unity-sdk](https://github.com/OnlyWorlds/unity-sdk), under the MIT licence.
Early
The package is public and in use, but it carries no compatibility promise yet, so expect the interface to change between releases. The [changelog](https://github.com/OnlyWorlds/unity-sdk/blob/main/Packages/com.onlyworlds.sdk/CHANGELOG.md) says what each version holds.
## Install
[Section titled “Install”](#install)
Unity 6 (6000.0) or later, with [Git](https://git-scm.com) installed: the Package Manager needs it to add a package from a git URL. In the Package Manager: **+ → Add package from git URL**, and paste:
```plaintext
https://github.com/OnlyWorlds/unity-sdk.git?path=/Packages/com.onlyworlds.sdk
```
To pin a release, add a tag from the [repository’s tags](https://github.com/OnlyWorlds/unity-sdk/tags) to the end of the URL (`…com.onlyworlds.sdk#v`). The package depends on Newtonsoft JSON (`com.unity.nuget.newtonsoft-json`), which the Package Manager resolves for you.
**Platforms**: the requests go through `UnityWebRequest`, and the JSON converters are marked to survive IL2CPP code stripping, which was checked by inspecting a stripped Android build. The package has not yet been run on a device, or in a WebGL build.
The package ships a **Quick Start** sample (Package Manager → OnlyWorlds SDK → Samples). It reads a world at runtime, from the API or from a cache asset, and shows nullable fields, link resolution and error handling.
## Read a World
[Section titled “Read a World”](#read-a-world)
No account yet? The demo key `0000000001` reads Moppetopia, a public sample world, with no PIN. It’s the same key the other SDKs start with, so the output matches theirs.
```csharp
using OnlyWorlds.Sdk;
using UnityEngine;
public class ReadWorld : MonoBehaviour
{
// A read key (ow_r_) needs no PIN. A key in a build can be extracted from it,
// so ship only a read key, and only for a world your players may read.
[SerializeField] private string apiKey = "0000000001";
private async void Start()
{
var client = new OWClient(new OWClientConfig
{
ApiKey = apiKey,
Transport = new UnityWebRequestTransport(),
});
var characters = await client.ListAllAsync("character");
foreach (var c in characters)
{
var level = c.Level.HasValue ? c.Level.Value.ToString() : "unset";
Debug.Log($"{c.Name}: level {level}");
}
}
}
```
`ListAllAsync` follows the cursor through every page. `ListAsync` returns one page, and `GetAsync` one element by id. See [Keys and PINs](/docs/getting-started/keys) for the kinds of key.
**Links are ids.** `c.Location` is a location’s id, or `null` when unset, and `c.Species` is a list of ids. A cache resolves them: `cache.Get(c.Location)`, `cache.Resolve(c.Species)`. Without a cache, you fetch each by id.
**Errors come in two kinds.** `OWApiError` means the server answered and refused: it carries `StatusCode`, `Code`, `Param`, `DocUrl` (a link into [Errors](/api/errors/)) and `IsRetryable`. `OWTransportError` means no answer arrived at all.
## Ship a World With Your Game
[Section titled “Ship a World With Your Game”](#ship-a-world-with-your-game)
A cache is a world stored as an asset in your project. Fill it in the Editor, and the build carries it: no network, no key and no waiting at runtime.
1. Set a key in **Window → OnlyWorlds → World Browser → Settings**. It is stored per machine in `EditorPrefs`, never in the project or in version control.
2. **Connect**, then **Sync to Cache**. The cache asset is written under `Assets/OnlyWorlds/`. **Open Folder…** fills a cache from a world folder on disk instead.
3. Reference the asset from your scripts:
```csharp
[SerializeField] private OWWorldCache world;
void Start()
{
foreach (var c in world.All("character"))
Debug.Log($"{c.Name} lives in {world.Get(c.Location)?.Name}");
}
```
The same steps run from code. `OWSync.BaselineAsync(client, cache)` fetches the whole world. `OWSync.IncrementalAsync(client, cache)` applies only what changed since the cache’s cursor, from the [changes feed](/docs/development/api/changes), and falls back to a full baseline when the cursor can’t be trusted. Edits to world metadata are not in the feed, so poll `GetWorldAsync` when those matter. `OWFolderLoader.LoadInto(cache, path)` reads a world folder and never writes to it.
## Write
[Section titled “Write”](#write)
Writes need a write key (`ow_w_`) and a secret in `OWClientConfig.ApiPin`: best an [agent seat](/docs/development/agents)’s own `ow_s_` secret, which works in one world and can be removed, or else the account PIN. Both can be extracted from a build, so write from the Editor, a server or a tool you control, never from a game you ship to players.
**Send only what changed.** A `PATCH` replaces every field it carries, so sending back an element fetched an hour ago undoes an hour of someone else’s edits. `OWEdit` snapshots an element and sends the difference:
```csharp
var edit = OWEdit.Begin(character);
character.Level = 12;
await edit.CommitAsync(client, "character"); // sends { "level": 12 } and nothing else
```
**Change link lists with `EditLinksAsync`.** A `PATCH` with a link list replaces the whole list. `EditLinksAsync("character", id, "friends", add: new[] { friendId })` adds and removes on the server, in one atomic step.
**Check every bulk write.** `BulkAsync` answers HTTP 200 even when some items failed. Read the result’s `Errors` and `Failed`, or call `ThrowIfAnyFailed()`.
**Other tools’ data survives.** Extension fields (`x_*`) that the models don’t know are kept verbatim through a read, an edit and a write, in the cache, and through Unity’s serializer. The five fields the server owns (`world`, `type`, `created_at`, `updated_at`, `change_seq`) are stripped from every write, so a body you read can be written straight back.
`OWFolderWriter` writes elements and the world into a world folder, byte for byte as the format specifies, so a folder kept in git doesn’t change from one machine to the next.
## Common Pitfalls
[Section titled “Common Pitfalls”](#common-pitfalls)
* **`""` is how a string field is unset.** The API never sends `null` for text. Test with `string.IsNullOrEmpty`, never `== null`. A single link is different: its unset value is `null`.
* **`null` is not `0`.** Nullable numbers are `SerializableNullable`, which keeps unset, a deliberate zero and absent apart. You can assign a plain `T`, but reading one makes you handle the unset case. The Inspector shows unset as `--`, never as `0`.
* **`UnityWebRequest` runs on the main thread only.** The client routes every request there, including the pages it fetches after an `await`. Never block on a request with `.Result` or `.Wait()` in the Editor: the completion arrives on the update loop, so blocking it deadlocks.
* **Only `name` is required.** Every other field can be empty, and the API sends the empty value (`null`, `""` or `[]`) rather than leaving the key out.
## Status
[Section titled “Status”](#status)
The package is early: public and in use, with no compatibility promise yet. All 22 element models are generated, and a drift check in the repository keeps them in step with the pinned schema. The package README holds more on each rule above, and the repository’s README explains how to work on the SDK itself and run its tests.
# Getting Started
> What OnlyWorlds is, and how to make a first read and write against a world.
This page is for developers and AI agents that read and write worlds through the API. To build a world without code, start at [onlyworlds.com/start](https://www.onlyworlds.com/start) or open [Atlas](https://atlas.onlyworlds.com).
OnlyWorlds is an open standard for world data. It structures a world into 22 element categories (Characters, Locations, Events and more) with fields and typed links between them, so any tool or AI agent that speaks the schema can read and write the same worlds.
It has four layers:
| Layer | What it is |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Standard** | The schema: 22 element categories defined in YAML, governed in public by the [Council](https://council.onlyworlds.com). See [Schema](/docs/schema/). |
| **Platform** | [onlyworlds.com](https://www.onlyworlds.com) stores worlds, their members and roles, and issues per-world keys. |
| **Interfaces** | The REST API, the change feed, the MCP server and agent join links. |
| **Clients** | Apps and tools (Atlas, the Obsidian plugin, converters), SDKs, and AI agents. |
A [video introduction](https://youtu.be/1IEazx8wg4I) covers the project as a whole.
## Your First Call
[Section titled “Your First Call”](#your-first-call)
### 1. Get a Key
[Section titled “1. Get a Key”](#1-get-a-key)
Sign up at [onlyworlds.com](https://www.onlyworlds.com/accounts/signup/) and create a world. Then mint a key for it in the [account portal](https://www.onlyworlds.com/account/):
| Key | Scope |
| -------- | ----------------------------------------------------- |
| `ow_w_…` | One world, read and write. Writes also need your PIN. |
| `ow_r_…` | One world, read only. No PIN, safe to share. |
Each key is scoped to one world: the key decides which world a request reads or writes, so you never send a world id. Older 10-digit keys still work and never expire, but new ones are no longer issued. [Keys and PINs](/docs/getting-started/keys) covers every key kind, the PIN and account tokens.
Send the key as the `API-Key` header, and the PIN as `API-Pin` on writes. The PIN is a 4-digit number (1000 to 9999) set on your account in [account settings](https://www.onlyworlds.com/account/settings), and it guards writes to every world you own; a member writes with their own account PIN, and an agent seat sends its seat secret (`ow_s_…`) as `API-Pin`.
### 2. Read
[Section titled “2. Read”](#2-read)
List the Characters in a world:
```bash
curl -H "API-Key: ow_r_your_key" \
"https://www.onlyworlds.com/api/v2/character/"
```
Lists come in an envelope: `{"data": [...], "has_more": false, "next_cursor": null}`. When `has_more` is true, pass `next_cursor` back as `?cursor=` for the next page. To try this before you have a world, use Hyperion’s demo key `0000000000`, which reads without a PIN ([Demo Keys](/docs/getting-started/keys#demo-keys) lists the others).
Every one of the 22 categories has the same routes at its singular name: `/api/v2/location/`, `/api/v2/event/`, and so on. [Reads and Pagination](/docs/development/api/reads) covers filters, sparse fields and expansion.
### 3. Write
[Section titled “3. Write”](#3-write)
Create a Character with a write key and the PIN. Only `name` is required: the key must be present, and an empty string is accepted.
```bash
curl -X POST "https://www.onlyworlds.com/api/v2/character/" \
-H "API-Key: ow_w_your_key" \
-H "API-Pin: 1234" \
-H "Content-Type: application/json" \
-d '{"name": "The Consul"}'
```
The response is `201` with the full element, including its server-assigned `id`. Change it with `PATCH`, sending only the fields you change. A link is the id of the element it points to:
```bash
curl -X PATCH "https://www.onlyworlds.com/api/v2/character//" \
-H "API-Key: ow_w_your_key" \
-H "API-Pin: 1234" \
-H "Content-Type: application/json" \
-d '{"description": "Diplomat of the Hegemony.", "location": ""}'
```
A read key on a write route answers `403` ([`permission_error`](/api/errors/#permission_error)). [Writes and Bulk](/docs/development/api/writes) covers upserts, deletes and clearing fields; [Link Fields](/docs/development/api/links) covers adding and removing links without reading first.
## Where to Go Next
[Section titled “Where to Go Next”](#where-to-go-next)
* **Build an app or script**: [SDKs](/docs/development/packages) for TypeScript, Python and Unity, or the [API reference](/docs/development/api-reference).
* **Connect an AI agent**: the [MCP server](/docs/development/mcp), agent seats, the LLM guide and the toolkit, under [AI Agents](/docs/development/ai).
* **Learn the data model**: [Schema](/docs/schema/), [Fields](/docs/schema/fields) and [Conventions](/docs/schema/conventions).
* **Build a world without code**: the guide at [onlyworlds.com/start](https://www.onlyworlds.com/start) covers which tool fits how you work and how to bring in existing material. [Atlas](https://atlas.onlyworlds.com) is the recommended workspace and keeps your world as plain files on your own disk; the [Obsidian plugin](https://github.com/OnlyWorlds/obsidian-plugin) syncs a world as markdown notes. Every tool is listed under [Tools](/docs/tools/).
* **Shape the schema**: motions and votes happen at [council.onlyworlds.com](https://council.onlyworlds.com).
# Keys and PINs
> The credentials the OnlyWorlds API accepts, which headers carry them, and when the PIN is needed.
A **world key** names one world and says what the caller may do in it. The **PIN** proves the caller may write. Both are managed in the [account portal](https://www.onlyworlds.com/account/), where keys are minted and revoked.
## Headers
[Section titled “Headers”](#headers)
```http
API-Key: {key}
API-Pin: {pin}
```
The header names are exactly `API-Key` and `API-Pin`. The key alone determines the world, so no request passes a world id.
## Key Types
[Section titled “Key Types”](#key-types)
| Key | What it is | Reads | Writes |
| ------------------- | ---------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `ow_w_…` | World write key | Key alone | Key and PIN |
| `ow_r_…` | World read key | Key alone | Refused: `403` [`permission_error`](/api/errors/#permission_error) |
| 10-digit legacy key | The original world key | Key alone; a private world also needs the PIN | Key and PIN |
| `ow_a_…` | Account token | Acts on your account, not on one world: see [Account Tokens](#account-tokens) | |
* Each world key (`ow_w_`, `ow_r_`, legacy) is scoped to one world.
* Legacy 10-digit keys keep working and stay valid. New ones are no longer issued: mint a prefixed key instead.
* A new key is shown once, when it is minted. Key lists afterwards show only its last four characters.
* `ow_r_` keys are made for sharing: hand one to players or readers and they can read the world without any second secret.
* Both the current API and the [Classic API](/docs/development/api/classic) accept prefixed and legacy keys.
## Demo Keys
[Section titled “Demo Keys”](#demo-keys)
The legacy keys `0000000000` to `0000000009` are reserved for demo worlds. They are read-only on every route (a write is `403` [`permission_error`](/api/errors/#permission_error)) and read without a PIN. These answer today:
| Key | World | Try |
| ------------ | ---------------------------------- | ----------------------------------------------- |
| `0000000000` | Hyperion, the public example world | `GET /api/v2/character?fields=id,name` |
| `0000000001` | Moppetopia | `GET /api/v2/character?name__icontains=admiral` |
The rest of the range is not for public use; unassigned keys answer `401` [`invalid_credentials`](/api/errors/#invalid_credentials).
## The PIN
[Section titled “The PIN”](#the-pin)
The PIN is a 4-digit number (1000 to 9999) set on your account in [account settings](https://www.onlyworlds.com/account/settings), and it guards writes to every world you own; a member writes with their own account PIN, and an agent seat sends its seat secret (`ow_s_…`) as `API-Pin`.
* **A world with a PIN requires it on every write.** New worlds always have one. An account without a PIN chooses it when creating its first world, and changing it in account settings changes it on every world the account owns.
* **Reads with a prefixed key (`ow_w_`, `ow_r_`) never need the PIN.** Only a legacy 10-digit key reading a private world must send it.
* Failed PIN attempts are throttled. Too many answer `429` [`rate_limited`](/api/errors/#rate_limited) with a `Retry-After` header, and repeated failures escalate to a temporary lockout.
Creating a world through the API with an account token: an account without a PIN sends `pin` (4 digits, 1000 to 9999) with its first world; an account that has one sends `name` only, and a `pin` alongside it is a `422`.
## Member Keys
[Section titled “Member Keys”](#member-keys)
A world can have members besides its owner (see [Members and Sharing](/docs/development/api/members)). A member mints their own keys for the world in their own account, and **a member’s key writes with the member’s own account PIN**, never the owner’s. A member without an account PIN cannot accept an invite or mint a write key: `409` [`pin_required`](/api/errors/#pin_required).
Removing a member, or a member leaving, deletes that member’s keys for the world.
## Agent Seat Keys
[Section titled “Agent Seat Keys”](#agent-seat-keys)
An AI agent that joins a world through an agent link receives, once, an `ow_w_` key and a seat secret (`ow_s_…`). The agent sends the secret as `API-Pin` on writes. It is never the world’s PIN or any account’s PIN, and reads take the key alone. Joining is described on [AI Agents](/docs/development/agents).
## Checking a Key
[Section titled “Checking a Key”](#checking-a-key)
`GET /api/v2/me` answers who the calling key is, for any key and without a PIN:
```bash
curl -s "https://www.onlyworlds.com/api/v2/me" -H "API-Key: {key}"
```
```json
{ "world": { "id": "0695…", "name": "Hyperion" }, "scope": "write", "kind": "owner",
"role": "owner", "membership": null, "character": "0698…" }
```
`GET /api/v2/world` also validates a key: a `200` means it is accepted.
### Checking the PIN
[Section titled “Checking the PIN”](#checking-the-pin)
No route checks a PIN without a write. `/me` and every read take the key alone (except a legacy key reading a private world), so a `200` there confirms the key, not the PIN. The PIN is checked on the first write, and a wrong PIN answers the same `401` [`invalid_credentials`](/api/errors/#invalid_credentials) as a bad key. The `message` tells them apart:
| Answer | `message` | Meaning |
| --------------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------- |
| `401` `invalid_credentials` | `No valid API-Key.` | The key is missing or unknown |
| `401` `invalid_credentials` | `Incorrect PIN.` | The key is valid; the PIN is missing or wrong |
| `401` [`key_revoked`](/api/errors/#key_revoked) | | The key was recognized but revoked |
| `403` [`permission_error`](/api/errors/#permission_error) | `This key is read-only and cannot write to this world.` | The key is genuine but lacks the scope, such as a read key on a write route |
| `429` [`rate_limited`](/api/errors/#rate_limited) | `Too many failed PIN attempts. Try again later.` | Too many wrong PINs: wait the `Retry-After` seconds |
Message wording may change: branch on `code`, and use the message only to tell a person what to fix.
## Keeping Credentials Safe
[Section titled “Keeping Credentials Safe”](#keeping-credentials-safe)
* A key and PIN in browser code are visible to anyone who opens the page. Ship only an `ow_r_` read key in a public page, or ask each visitor for their own credentials at runtime. See [CORS](/docs/development/api/cors).
* Keep keys and PINs in a `.env` file that git ignores, never in a commit or a chat.
* An MCP client stores the `API-Key` and `API-Pin` headers in its own configuration as written. Use a key you can revoke on its own. See [MCP Server](/docs/development/mcp).
* An agent seat’s secret is shown once, at join. See [AI Agents](/docs/development/agents).
* A leaked key is revoked in the account portal; mint a new one in its place.
## Account Tokens
[Section titled “Account Tokens”](#account-tokens)
An `ow_a_` token acts on your account: it lists your worlds, creates worlds, mints and revokes world keys, and manages invites, members and watched worlds. Mint one in the account portal under **Settings → Account tokens**, or with `POST /api/v2/account/tokens`. Like a key, it is shown once.
It is sent as a bearer token, not as `API-Key`:
```bash
curl -s "https://www.onlyworlds.com/api/v2/account/worlds" \
-H "Authorization: Bearer ow_a_…"
```
| Method | Route | Purpose |
| ---------- | ------------------------------------------------- | ---------------------------------------------------------------------- |
| GET, PATCH | `/api/v2/account` | Your profile |
| GET | `/api/v2/account/worlds` | The worlds you own and the worlds shared with you |
| POST | `/api/v2/account/worlds` | Create a world: `201` with the world and a new `ow_w_` key, shown once |
| POST | `/api/v2/account/worlds/{world_id}/keys` | Mint a world key, shown once |
| GET | `/api/v2/account/worlds/{world_id}/keys` | List a world’s keys (last four characters only) |
| DELETE | `/api/v2/account/worlds/{world_id}/keys/{key_id}` | Revoke a world key |
| POST | `/api/v2/account/tokens` | Mint an account token, shown once |
| GET | `/api/v2/account/tokens` | List your account tokens (last four characters only) |
| DELETE | `/api/v2/account/tokens/{token_id}` | Revoke an account token |
Minting a world key takes `scope` (`"read"` or `"write"`, default `"write"`) and an optional `name`, a label such as `"atlas · my laptop"` that is echoed back in the key list. A `name` over 255 characters is a `422`; the same limit applies to account token names.
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/account/worlds/{world_id}/keys" \
-H "Authorization: Bearer ow_a_…" -H "Content-Type: application/json" \
-d '{ "scope": "read", "name": "players" }'
```
```json
{ "id": "…", "name": "players", "last4": "…", "scope": "READ",
"last_used_at": null, "revoked": false, "key": "ow_r_…" }
```
`key` appears in this response only. Creating a world answers its summary with a new write key the same way:
```bash
curl -s -X POST "https://www.onlyworlds.com/api/v2/account/worlds" \
-H "Authorization: Bearer ow_a_…" -H "Content-Type: application/json" \
-d '{ "name": "Hyperion" }'
```
```json
{ "id": "…", "name": "Hyperion", "role": "owner", "public_read": false,
"keys": [ { "name": "", "last4": "…", "scope": "WRITE", "revoked": false } ],
"key": "ow_w_…" }
```
The routes for invites, members and watched worlds are on [Members and Sharing](/docs/development/api/members#managing-members). A missing or invalid account credential answers `401`.
# Schema
> The OnlyWorlds standard, its 22 element categories, typed links, and where the schema is defined and governed.
The OnlyWorlds schema is the standard every OnlyWorlds tool and world shares. It defines 22 element **categories**, the fields each one carries, and typed links between them. An **element** is one record in a world: a Character, a Location, a Law.
Use as much of it as you need. Only `name` is required: the key must be present, and an empty string is accepted, on every category, Pins and Markers included. So a world of nothing but Objects is a valid world. Supertype and subtype add a world’s own categories, `x_` fields carry data the schema doesn’t model, and high-volume data can live in your own database beside the world, keyed by element id.
## The 22 Categories
[Section titled “The 22 Categories”](#the-22-categories)
| Category | Definition |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Character](/docs/schema/element_categories/character) | A Character represents an individual with agency and the capacity to make choices that affect their world. |
| [Creature](/docs/schema/element_categories/creature) | Creatures are living entities within a world that exhibit behavior and agency but lack the strategic reasoning, narrative focus, or social complexity of Characters. |
| [Species](/docs/schema/element_categories/species) | A Species defines a distinct biological or cultural form of life. |
| [Family](/docs/schema/element_categories/family) | A Family is a group tied together by lineage, heritage or community. |
| [Collective](/docs/schema/element_categories/collective) | A Collective is a group of individuals that acts as a unit but lacks formal governance or hierarchical structure. |
| [Institution](/docs/schema/element_categories/institution) | Institutions are organized bodies with purpose and structure. |
| [Location](/docs/schema/element_categories/location) | A Location represents a distinct place within the world where activities occur and elements converge. |
| [Object](/docs/schema/element_categories/object) | Objects are tangible, non-living things that can be made, owned, traded or destroyed. |
| [Construct](/docs/schema/element_categories/construct) | Constructs are abstract or conceptual structures that exist within a world and can have causal, symbolic, or systemic roles. |
| [Ability](/docs/schema/element_categories/ability) | An Ability represents something an entity in the world can do: a defined action, power, skill, or effect that can change the world or its perception. |
| [Trait](/docs/schema/element_categories/trait) | Traits describe qualities that shape how a character or creature acts, responds, or is perceived. |
| [Title](/docs/schema/element_categories/title) | A Title is a formal designation that confers identity, standing, or power within a world. |
| [Language](/docs/schema/element_categories/language) | Languages are systems of shared meaning, whether natural, constructed, or symbolic. |
| [Law](/docs/schema/element_categories/law) | A Law represents a formalized rule or set of guidelines that governs the actions of individuals or groups within a specific jurisdiction. |
| [Event](/docs/schema/element_categories/event) | An Event represents a time-bound happening within the world. |
| [Narrative](/docs/schema/element_categories/narrative) | Narratives represent stories told in your world, and can involve the organization or reinterpretation of Events. |
| [Phenomenon](/docs/schema/element_categories/phenomenon) | Phenomena are ongoing or emergent conditions that act in or upon the world. |
| [Relation](/docs/schema/element_categories/relation) | Relations are non-material and capture meaningful connections between world elements. |
| [Map](/docs/schema/element_categories/map) | A Map represents a spatial template that defines a coordinate system for placing elements within your world. |
| [Pin](/docs/schema/element_categories/pin) | Pins represent a single element on a single Map, indicating its position in that particular world view. |
| [Marker](/docs/schema/element_categories/marker) | Groups of Markers, each at a specific coordinate, together designate a Zone in the world. |
| [Zone](/docs/schema/element_categories/zone) | Zones represent abstract or meaningful areas within the world that hold significance due to cultural, political, environmental, or narrative reasons. |
Every element also carries the same base fields (name, description, supertype, subtype, image and more). [Fields](/docs/schema/fields) lists them, with the field types and what is required. [Worlds](/docs/schema/worlds) covers the container that holds the elements and its timeline. [A Worked Example](/docs/schema/example) reads one small world through the API, link by link.
## Typed Links
[Section titled “Typed Links”](#typed-links)
Links are typed fields. A Character’s `location` is a single link that holds one Location; its `friends` is a multi link that holds any number of Characters. Each link field names the category it points to, so a tool always knows what kind of element is on the other end. One field, the Pin’s `element`, is a generic link that can point to an element of any category.
Links point one way, from the element that holds the field. For connections that carry their own history, span or intensity, the schema has a category of its own: [Relation](/docs/schema/element_categories/relation). [Conventions](/docs/schema/conventions) covers when to use which.
## Where the Schema Lives
[Section titled “Where the Schema Lives”](#where-the-schema-lives)
| What | Where |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The standard, as YAML | [github.com/OnlyWorlds/OnlyWorlds](https://github.com/OnlyWorlds/OnlyWorlds/tree/main/schema): one file per category, plus `base_properties.yaml` for the fields every element shares and a `VERSION` file |
| Governance | [council.onlyworlds.com](https://council.onlyworlds.com): the schema changes in public, by motion and vote. Take part on the site or through the toolkit. |
| For tool builders | [schema-dist](https://github.com/OnlyWorlds/schema-dist): the generated distribution, its decoder (the walk), and the ruling table. Vendor it rather than writing a parser of your own. |
# Conventions
> How to model a world with the categories and fields the schema already has.
The schema covers most worlds with its 22 categories and their fields. Before reaching for a new field or category, check whether one of these conventions carries the case. When none does, the [Council](https://council.onlyworlds.com) is where the schema changes.
## Use Only What You Need
[Section titled “Use Only What You Need”](#use-only-what-you-need)
A world can use any subset of the categories. Only `name` is required on an element, so a world of nothing but Objects is a valid world, and most tools work with two to four categories.
## Supertype and Subtype Are the World’s Own Categories
[Section titled “Supertype and Subtype Are the World’s Own Categories”](#supertype-and-subtype-are-the-worlds-own-categories)
Every element has two free-text classification fields: `supertype`, the top-level category the element belongs to, and `subtype`, a further classification within it. The schema fixes no values, so each world defines its own: a Location with supertype `City` and subtype `Port`, an Institution with supertype `Guild`.
The API filters on both by exact value (`?supertype=City`), so keep the vocabulary consistent within a world. Some platform features use supertypes too: an agent’s own Character has supertype `Agent`, and in-world messages are Narratives with supertype `Message` (see below).
## Links and Relations
[Section titled “Links and Relations”](#links-and-relations)
A link field states that a connection exists: a Character’s `location`, an Event’s `characters`. Links point one way, from the element that holds the field.
When the connection itself has content, make it a [Relation](/docs/schema/element_categories/relation). A Relation is an element of its own, so it can carry what a link cannot:
| Relation field | Holds |
| -------------------------------------------- | ------------------------------------------------ |
| `actor` | The primary Character defining the relation |
| `background` | History and origin of the relation |
| `start_date`, `end_date` | When it began and ended, in world time units |
| `intensity` | Significance, 0 to 100 |
| `events` | Events where the relation is involved |
| `characters`, `institutions`, `locations`, … | The elements it connects, across most categories |
If the Consul’s ties to the Hegemony have a history and a span worth recording, they are a Relation; the Consul’s current location is a link.
## Choosing Between Neighbouring Categories
[Section titled “Choosing Between Neighbouring Categories”](#choosing-between-neighbouring-categories)
The category definitions draw these lines themselves:
| Pair | The line |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Character / Creature | A Character is “an individual with agency and the capacity to make choices”; Creatures “lack the strategic reasoning, narrative focus, or social complexity of Characters.” |
| Institution / Collective | Institutions are “organized bodies with purpose and structure”; a Collective “acts as a unit but lacks formal governance or hierarchical structure.” |
| Species / Collective | A Species is “a distinct biological or cultural form of life”; a Collective is “unified by shared traits, contexts, or purpose.” |
| Object / Construct | Objects are “tangible, non-living things that can be made, owned, traded or destroyed”; Constructs are “non-physical, non-character entities that help explain or organize how the world works.” |
| Phenomenon / Construct | Phenomena are “ongoing or emergent conditions… not defined by intent or agency”; Constructs are “frameworks that are made, sustained and lost over time.” |
| Trait / Ability | Traits are “not active powers or learned abilities, but underlying aspects of identity”; an Ability is “a defined action, power, skill, or effect.” |
| Event / Narrative | An Event is “a time-bound happening within the world”; Narratives are “stories told in your world, and can involve the organization or reinterpretation of Events.” |
| Location / Zone | A Location is “a distinct place within the world where activities occur and elements converge”; Zones “are not defined by geometry themselves but gain spatial presence through linked map markers.” |
## Zones Take Their Shape From Markers
[Section titled “Zones Take Their Shape From Markers”](#zones-take-their-shape-from-markers)
A Zone has no geometry of its own. Its shape on a [Map](/docs/schema/element_categories/map) is a set of [Markers](/docs/schema/element_categories/marker), each linked to the Map and the Zone, at an `x`/`y` coordinate, with an `order` that sequences them into a polygon or line (`0` is the first point). Markers are boundary points and usually carry an empty name. A [Pin](/docs/schema/element_categories/pin) places one element of any category at one point on one Map.
## Time and Units
[Section titled “Time and Units”](#time-and-units)
Dates are integers in the world’s own time units, set on the [world](/docs/schema/worlds): a Character’s `birth_date`, an Event’s `start_date` and `end_date`, a Relation’s span. Height and weight use the world’s length and mass units. All numbers are whole, and the API truncates decimals, so choose units small enough that values come out whole.
## Story and Description
[Section titled “Story and Description”](#story-and-description)
On a [Narrative](/docs/schema/element_categories/narrative), `story` holds the content of the narrative, as told or remembered. `description`, the base field every element has, holds details about it. Put the text of a tale, a chronicle or a message in `story`, and an account of it in `description`.
## Messages Between Members
[Section titled “Messages Between Members”](#messages-between-members)
In-world messages are [Narratives](/docs/schema/element_categories/narrative) with supertype `Message`. The `narrator` is the Character credited with the message, and `characters` names its recipients, so a member’s inbox is one filtered read:
```http
GET /api/v2/narrative/?supertype=Message&characters=
```
`narrator` is what a message claims; the element’s `created_by` is what the server recorded. [Members](/docs/development/api/members) covers the roster that joins the two.
## Data the Schema Does Not Model
[Section titled “Data the Schema Does Not Model”](#data-the-schema-does-not-model)
Two places hold what the categories have no field for:
* **Extension fields** (`x_*`) on the element itself, for small amounts of tool-specific data. They are stored and returned verbatim, up to 64 KB per element. See [Fields](/docs/schema/fields#extension-fields).
* **Your own database**, beside the world, for high-volume data such as logs, statistics or simulation state. Key each row by the element’s `id`.
Data in either place travels only to the tools that read it. Anything another tool should understand belongs in the schema’s own fields.
# Ability
> An Ability represents something an entity in the world can do: a defined action, power, skill, or effect that can change the world or its perception.

An Ability represents something an entity in the world can do: a defined action, power, skill, or effect that can change the world or its perception. Abilities are performable and traceable: they have causes, consequences, and conditions. They can be passive traits, active powers, or conditional techniques.
An Ability is a performable effect or power. They interact with:
* **Characters and Creatures** (as actors who employ them)
* **Traits** (which may grant or enhance them)
* **Objects and Constructs** (that might enable or constrain them)
* **Phenomena** (which may result from or trigger them)
* **Institutions** (which can teach or restrict them)
They are distinct from:
* **Traits** (which describe qualities, not actions)
* **Events** (which describe outcomes or occurrences, not repeatable powers)
[Ability discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/ability)
## Fields
[Section titled “Fields”](#fields)
### Mechanics
[Section titled “Mechanics”](#mechanics)
| Field | Type | Description |
| ------------ | ------------------- | ------------------------------------------------------------------------------------------------------------- |
| `activation` | text | Method or conditions under which the ability is activated |
| `duration` | integer | Length of time the ability remains active or its effects persist, measured in TIME units |
| `potency` | integer | Relative measure of the ability’s inherent potency or force, used for scaling or comparison purposes |
| `range` | integer | Effective reach or distance at which the ability can be used, measured in DISTANCE units |
| `effects` | links to Phenomenon | Phenomena that result from the ability’s use, such as environmental changes or sensory effects |
| `challenges` | text | Describes specific difficulties or constraints that make the ability hard to master or use effectively |
| `talents` | links to Trait | Traits that naturally enhance or improve performance with this ability |
| `requisites` | links to Construct | Constructs that must be satisfied for the ability to be used, such as rituals, permissions, or required roles |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------- | ------------------ | ------------------------------------------------------------------------------------------------------- |
| `prevalence` | text | How widely the ability is known or practiced, and potential clues to its origins and cultural diffusion |
| `tradition` | link to Construct | A construct that expresses the conceptual, social, or institutional system this ability operates within |
| `source` | link to Phenomenon | The phenomenon that serves as the enabling force or condition that allows this ability to function |
| `locus` | link to Location | Location where the ability is most strongly rooted, developed, or traditionally practiced |
| `instruments` | links to Object | Objects or tools required to activate, channel, or perform the ability |
| `systems` | links to Construct | Magic frameworks or structures that the ability associates with |
# Character
> A Character represents an individual with agency and the capacity to make choices that affect their world.

A Character represents an individual with agency and the capacity to make choices that affect their world. Characters are self-directed actors who can respond to situations, form relationships, and drive narrative change through their decisions and actions.
Its own link fields point to: **Species, Traits and Abilities** (`species`, `traits`, `abilities`); **Languages and Objects** (`languages`, `objects`); **Locations** (`birthplace`, `location`); **Institutions and Families** (`institutions`, `family`); **other Characters** (`friends`, `rivals`). Other elements link to a Character from their side: an Event, Collective, Construct, Narrative, Relation or Title lists it in `characters`, and a Relation names it as `actor`. Find those with a filter, e.g. `GET /api/v2/event?characters={id}`.
They are distinct from:
* **Creatures** (which lack reasoned choices or structured goals)
* **Collectives** (which model group entities without individual agency)
[Character discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/character)
Every field below is optional. The TTRPG group (`level`, `hit_points` and six ability scores) is for worlds that run a game system. `charisma` (Personality) and `CHA` (TTRPG) are separate fields.
## Fields
[Section titled “Fields”](#fields)
### Constitution
[Section titled “Constitution”](#constitution)
| Field | Type | Description |
| ------------- | ---------------- | --------------------------------------------------------------------- |
| `physicality` | text | The character’s visible physical features and body attributes |
| `mentality` | text | The character’s mindset, emotional tone, and style of thinking |
| `height` | integer | The character’s approximate or exact height, using world LENGTH units |
| `weight` | integer | The character’s approximate or exact weight, using world MASS units |
| `species` | links to Species | Species the character might belong to |
| `traits` | links to Trait | Traits for notable behavioral, physical, or systemic characteristics |
| `abilities` | links to Ability | Abilities the character might perform, control, or invoke |
### Origins
[Section titled “Origins”](#origins)
| Field | Type | Description |
| ------------- | ----------------- | ------------------------------------------------------------------------------ |
| `background` | text | History, upbringing, or formative experiences of the character |
| `motivations` | text | Core desires, goals, or values that drive the character’s choices and behavior |
| `birth_date` | integer | Moment of birth, expressed in the world’s TIME units |
| `birthplace` | link to Location | Location where the character was born |
| `languages` | links to Language | Languages the character can understand, speak, or use for communication |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| -------------- | -------------------- | ------------------------------------------------------------------------ |
| `reputation` | text | Brief summary of the character’s current condition, role, or predicament |
| `location` | link to Location | The character’s present physical location |
| `objects` | links to Object | Key objects owned by or symbolically linked to the character |
| `institutions` | links to Institution | Institutions the character is affiliated with |
### Personality
[Section titled “Personality”](#personality)
| Field | Type | Description |
| ------------ | ------- | ---------------------------------------------------------------------------- |
| `charisma` | integer | Ability to attract, inspire, and influence others |
| `coercion` | integer | Capacity to dominate, intimidate, or apply force to shape outcomes |
| `competence` | integer | Skill in planning, understanding, and managing complex systems or situations |
| `compassion` | integer | Willingness to empathize with and care for others |
| `creativity` | integer | Ability to generate novel ideas, perspectives, or solutions |
| `courage` | integer | Readiness to face danger, risk, or adversity |
### Social
[Section titled “Social”](#social)
| Field | Type | Description |
| --------- | ------------------ | -------------------------------------------------------------------- |
| `family` | links to Family | Families the character belongs to by blood or adoption |
| `friends` | links to Character | Characters the character considers close allies or companions |
| `rivals` | links to Character | Characters the character is in active opposition or competition with |
### TTRPG
[Section titled “TTRPG”](#ttrpg)
| Field | Type | Description |
| ------------ | ------- | -------------------------------------------------- |
| `level` | integer | Progression rank of the character in a game system |
| `hit_points` | integer | Total health available to the character |
| `STR` | integer | Physical force and carrying capacity |
| `DEX` | integer | Agility, coordination, and reflexes |
| `CON` | integer | Endurance and resistance to strain |
| `INT` | integer | Reasoning, memory, and learning |
| `WIS` | integer | Intuition, awareness, and judgment |
| `CHA` | integer | Persuasiveness and personal magnetism |
# Collective
> A Collective is a group of individuals that acts as a unit but lacks formal governance or hierarchical structure.

A Collective is a group of individuals that acts as a unit but lacks formal governance or hierarchical structure. Collectives range from spontaneous mobs to herds of animals, and are unified by shared traits, contexts, or purpose.
A Collective is a loosely organized group defined by common identity or circumstance, not by structure or command. They interact with:
* **Characters and Creatures** (who form their body)
* **Species** (defining their biological foundation(s))
* **Constructs and Abilities** (shaping what they do and believe)
* **Institutions** (which may organize or employ them)
* **Phenomena** (that bind or affect them)
They are distinct from:
* **Institutions** (which govern, plan, and wield power)
* **Families** (which define genetic or cultural lineage)
[Collective discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/collective)
## Fields
[Section titled “Fields”](#fields)
### Formation
[Section titled “Formation”](#formation)
| Field | Type | Description |
| ---------------- | ------------------- | ---------------------------------------------------------------------- |
| `composition` | text | Internal structure or demographic makeup of the collective |
| `count` | integer | Number of members in the collective (approximate or exact) |
| `formation_date` | integer | Date the collective was formed, using world TIME units |
| `operator` | link to Institution | Institution that manages or directs the collective |
| `equipment` | links to Object | Tools or gear in possession of and/or regularly used by the collective |
### Dynamics
[Section titled “Dynamics”](#dynamics)
| Field | Type | Description |
| ------------- | ------------------ | ----------------------------------------------------------------------------------------------------- |
| `activity` | text | Primary behaviors or actions the collective engages in |
| `disposition` | text | Emotional control or volatility expressed by the collective |
| `state` | text | Current condition or operational status of the collective |
| `abilities` | links to Ability | Abilities commonly shared among members of the collective, or abilities of that collective as a whole |
| `symbolism` | links to Construct | Cultural expressions, rituals, or symbols that unify or distinguish the collective |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------ | ------------------- | ------------------------------------------------------- |
| `species` | links to Species | Species that compose or participate in the collective |
| `characters` | links to Character | Characters who are members of the collective |
| `creatures` | links to Creature | Creatures associated with or included in the collective |
| `phenomena` | links to Phenomenon | Phenomena that influence or characterize the collective |
# Construct
> Constructs are abstract or conceptual structures that exist within a world and can have causal, symbolic, or systemic roles.

Constructs are abstract or conceptual structures that exist within a world and can have causal, symbolic, or systemic roles. They are non-physical, non-character entities that help explain or organize how the world works. Constructs shape cultures, behaviors, and institutions by providing internal logic or justification for how things work. They are not universal truths, but frameworks that are made, sustained and lost over time.
Constructs describe structured ideas or systems that the world acknowledges or operates through. They interact with:
* **Characters** (who invent, oppose, or live by them)
* **Institutions** (that organize, enforce, or exploit them)
* **Objects and Abilities** (which might manifest or otherwise relate to them)
* **Languages, Titles, and Traits** (to carry or symbolize them)
They are distinct from:
* **Laws** (which are more externalized, often enforceable by rule)
* **Phenomena** (which are observed and natural rather than believed or structured)
* **Narratives** (which explain or contain constructs)
[Construct discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/construct)
## Fields
[Section titled “Fields”](#fields)
### Nature
[Section titled “Nature”](#nature)
| Field | Type | Description |
| ------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `rationale` | text | The internal reasoning, structure, or justification of how the construct functions or makes sense within the world |
| `history` | text | The historical development or ideation of the construct, and its place in wider historical contexts |
| `status` | text | The present condition or operational status of the construct |
| `reach` | text | The geographic, cultural, or political extent of the construct’s influence |
| `start_date` | integer | The point in time when the construct began or was first established (uses world’s TIME definition) |
| `end_date` | integer | The point in time when the construct ceased to function or lost its meaning |
| `founder` | link to Character | Character who conceived or initiated the construct |
| `custodian` | link to Institution | Institution maintaining, enforcing, or exploiting the construct |
### Involves
[Section titled “Involves”](#involves)
| Field | Type | Description |
| -------------- | -------------------- | ------------------------------------------ |
| `characters` | links to Character | Characters relevant to the construct |
| `objects` | links to Object | Objects relevant to the construct |
| `locations` | links to Location | Locations relevant to the construct |
| `species` | links to Species | Species relevant to the construct |
| `creatures` | links to Creature | Creatures relevant to the construct |
| `institutions` | links to Institution | Institutions relevant to the construct |
| `traits` | links to Trait | Traits relevant to the construct |
| `collectives` | links to Collective | Collectives relevant to the construct |
| `zones` | links to Zone | Zones relevant to the construct |
| `abilities` | links to Ability | Abilities relevant to the construct |
| `phenomena` | links to Phenomenon | Phenomena relevant to the construct |
| `languages` | links to Language | Languages relevant to the construct |
| `families` | links to Family | Families relevant to the construct |
| `relations` | links to Relation | Relations relevant to the construct |
| `titles` | links to Title | Titles relevant to the construct |
| `constructs` | links to Construct | Other constructs relevant to the construct |
| `events` | links to Event | Events relevant to the construct |
| `narratives` | links to Narrative | Narratives relevant to the construct |
# Creature
> Creatures are living entities within a world that exhibit behavior and agency but lack the strategic reasoning, narrative focus, or social complexity of Characters.

Creatures are living entities within a world that exhibit behavior and agency but lack the strategic reasoning, narrative focus, or social complexity of Characters. Their role is typically reactive over proactive, but they can still play important parts in a world’s ecology, mythology, or atmosphere.
Creatures help populate the world with living presence. They interact with:
* **Species** (defining their biological origin or variation)
* **Traits and Abilities** (for what they can do or how they can act)
* **Locations and Zones** (the spaces they inhabit)
* **Characters and Institutions** (as caretakers, enemies, or breeders)
They are distinct from:
* **Characters** (who possess intent, narrative relevance, and structured goals)
* **Objects** (which are inanimate)
* **Phenomena** (which are forces or occurrences, not beings)
[Creature discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/creature)
## Fields
[Section titled “Fields”](#fields)
### Biology
[Section titled “Biology”](#biology)
| Field | Type | Description |
| ------------ | ---------------- | -------------------------------------------------------------------------- |
| `appearance` | text | Visual description of the creature |
| `weight` | integer | Approximate or exact weight of the creature, using world MASS units |
| `height` | integer | Approximate height of the creature, using the world’s defined LENGTH units |
| `species` | links to Species | Species this creature belongs to |
### Behavior
[Section titled “Behavior”](#behavior)
| Field | Type | Description |
| ----------- | ----------------- | ---------------------------------------------------------------------------------------------- |
| `habits` | text | Typical behaviors, instincts, or recurring actions the creature tends to display |
| `demeanor` | text | The emotional tone or attitude the creature conveys through posture, expression, or aggression |
| `traits` | links to Trait | Traits that influence the creature’s behavior, capabilities, or appearance |
| `abilities` | links to Ability | Innate or learned abilities the creature can perform or activate |
| `languages` | links to Language | Languages the creature can understand, speak, or otherwise use to communicate |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------ | ---------------- | ------------------------------------------------------------------------------- |
| `status` | text | Current situation or classification of the creature |
| `birth_date` | integer | The time of the creature’s birth, recorded in the world’s defined TIME unit |
| `location` | link to Location | Specific location where the creature is currently found or most associated with |
| `zone` | link to Zone | Larger area or region commonly inhabited or currently claimed by the creature |
### TTRPG
[Section titled “TTRPG”](#ttrpg)
| Field | Type | Description |
| ------------------ | ---------------- | ----------------------------------------------------------------------- |
| `challenge_rating` | integer | Difficulty or threat level of the creature in a gameplay context |
| `hit_points` | integer | Total health or durability value in combat |
| `armor_class` | integer | Defense rating against physical attacks or effects |
| `speed` | integer | Typical movement speed, measured in the world’s DISTANCE unit per round |
| `actions` | links to Ability | Combat or tactical abilities the creature can perform or use |
# Event
> An Event represents a time-bound happening within the world.

An Event represents a time-bound happening within the world. Events capture notable incidents—historical, natural, or supernatural—that hold significance for the world or its inhabitants. They define what happens, when it happens, and who or what is involved.
Events are defined occurrences. They pull in and affect:
* **Characters, Collectives, and Institutions** (as agents or sufferers)
* **Zones and Locations** (as settings or battlegrounds)
* **Constructs and Laws** (as outcomes or contested forces)
* **Traits, Titles, and Abilities** (as qualities or catalysts)
* **Phenomena, Creatures, and Objects** (as elements in motion)
They are distinct from:
* **Narratives** (which reinterpret potential collections of Events)
* **Constructs** (which represent ideas or systems that persist over time)
* **Relations** (which define ongoing bonds or tensions between actors)
Events are about what happens. The rest of the schema gives it meaning, context, and consequence.
[Event discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/event)
## Fields
[Section titled “Fields”](#fields)
### Nature
[Section titled “Nature”](#nature)
| Field | Type | Description |
| -------------- | -------------- | ------------------------------------------------ |
| `history` | text | Historical context and background of the event |
| `challenges` | text | Adversity or difficulties faced during the event |
| `consequences` | text | Outcomes and impacts resulting from the event |
| `start_date` | integer | Date on which the event began |
| `end_date` | integer | Date on which the event concluded |
| `triggers` | links to Event | Events that precipitated this event |
### Involves
[Section titled “Involves”](#involves)
| Field | Type | Description |
| -------------- | -------------------- | ---------------------------------------------------------- |
| `characters` | links to Character | Key characters relevant to the event |
| `objects` | links to Object | Objects relevant to the event |
| `locations` | links to Location | Locations relevant to the event |
| `species` | links to Species | Species relevant to the event |
| `creatures` | links to Creature | Creatures relevant to the event |
| `institutions` | links to Institution | Institutions relevant to the event |
| `traits` | links to Trait | Traits relevant to the event |
| `collectives` | links to Collective | Groups or collectives relevant to the event |
| `zones` | links to Zone | Zones relevant to the event |
| `abilities` | links to Ability | Abilities relevant to the event |
| `phenomena` | links to Phenomenon | Natural or supernatural phenomena relevant to the event |
| `languages` | links to Language | Languages relevant to the event |
| `families` | links to Family | Families relevant to the event |
| `relations` | links to Relation | Interpersonal or political relations relevant to the event |
| `titles` | links to Title | Titles relevant to the event |
| `constructs` | links to Construct | Concepts, laws, or built entities relevant to the event |
# Family
> A Family is a group tied together by lineage, heritage or community.

A Family is a group tied together by lineage, heritage or community.
A Family is a group tied together by lineage, heritage or community. They interact with:
* **Characters** (who are members, descendants or ancestors)
* **Traits, Abilities, and Languages** (passed down or shared)
* **Objects and Creatures** (as heirlooms or symbols)
* **Institutions and Locations** (which they govern or inhabit)
They are distinct from:
* **Institutions** (which organize for more general purposes)
* **Collectives** (which group by circumstance, not inheritance)
[Family discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/family)
## Fields
[Section titled “Fields”](#fields)
### Identity
[Section titled “Identity”](#identity)
| Field | Type | Description |
| ------------ | ------------------ | ---------------------------------------------------------------- |
| `spirit` | text | The core values or shared ethos that the family embodies |
| `history` | text | Background or origin story of the family |
| `traditions` | links to Construct | Cultural practices, symbols, or customs overseen by the family |
| `traits` | links to Trait | Traits possibly found among members of the family |
| `abilities` | links to Ability | Abilities or special qualities possibly present in the family |
| `languages` | links to Language | Languages spoken by, or associated with the family |
| `ancestors` | links to Character | Notable forebears or historic characters in the family’s lineage |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------ | -------------------- | ----------------------------------------------------------------- |
| `reputation` | text | Current social, political, or general standing of the family |
| `estates` | links to Location | Key locations owned, governed, or symbolically tied to the family |
| `governs` | links to Institution | Institutions administered or managed by the family |
| `heirlooms` | links to Object | Important objects or artifacts handed down by the family |
| `creatures` | links to Creature | Creatures owned, bonded to, or representing the family |
# Institution
> Institutions are organized bodies with purpose and structure.

Institutions are organized bodies with purpose and structure. They shape the world through policy, organization, and culture. Institutions can serve as key agents of power, coordinating individuals and collectives around shared goals, practices, or ideologies.
Institutions are executive structures of meaning in a world. They interact with:
* **Characters and Titles** (placing people in institutions)
* **Zones, Objects, and Creatures** (what they define or control)
* **Laws and Constructs** (what they enforce, create or utilize)
They are distinct from:
* **Collectives** (which model informal or non-agentic groups)
* **Families** (which have implications of kinship and ancestry)
[Institution discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/institution)
## Fields
[Section titled “Fields”](#fields)
### Foundation
[Section titled “Foundation”](#foundation)
| Field | Type | Description |
| -------------------- | ------------------- | --------------------------------------------------------------------- |
| `doctrine` | text | Core belief, mission, or purpose that drives the institution |
| `founding_date` | integer | Date when the institution was established, in the world’s TIME format |
| `parent_institution` | link to Institution | Institution that governs, embodies, or originated this one |
### Claims
[Section titled “Claims”](#claims)
| Field | Type | Description |
| ----------- | ----------------- | ---------------------------------------------------------------------------------------- |
| `zones` | links to Zone | Areas the institution controls or claims authority over |
| `objects` | links to Object | Significant objects owned or tied to the institution’s operations, holdings, or identity |
| `creatures` | links to Creature | Creatures under the institution’s protection, use, or symbolic control |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------- | -------------------- | -------------------------------------------------------------------------------------- |
| `status` | text | Current political, cultural, or functional standing of the institution in the world |
| `allies` | links to Institution | Institutions this one actively cooperates or aligns with |
| `adversaries` | links to Institution | Institutions this one opposes, competes with, or is in conflict with |
| `constructs` | links to Construct | Conceptual, procedural, or structural systems created or maintained by the institution |
# Language
> Languages are systems of shared meaning, whether natural, constructed, or symbolic.

Languages are systems of shared meaning, whether natural, constructed, or symbolic. This category captures their role within a world: how they sound, how they are written, how they function grammatically, and where or by whom they are used. Languages may carry political weight, cultural importance, or fictional depth. The category’s fields are designed to support both lightweight notation and full conlang development.
Languages are a world’s methods of communication. They interact with:
* **Characters, Species, and Creatures** (who speak, shape, or inherit them)
* **Locations and Zones** (where languages are regionally dominant or isolated)
* **Constructs** (for classifying broader typological or ancestral groups)
They are distinct from:
* **Constructs** (which include ideas, systems, or tools not tied to speech)
* **Traits** (which describe how a character expresses themselves, but not the system of expression)
[Language discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/language)
## Fields
[Section titled “Fields”](#fields)
### Structure
[Section titled “Structure”](#structure)
| Field | Type | Description |
| ---------------- | ----------------- | ------------------------------------------------------------------------------- |
| `phonology` | text | The language’s sound systems, including phonemes, tone, and pronunciation rules |
| `grammar` | text | Rules governing syntax, morphology, and sentence structure |
| `lexicon` | text | Vocabulary principles or full word lists used in the language |
| `writing` | text | Script or notation system used to represent the language in written form |
| `classification` | link to Construct | Linguistic group or typological category the language belongs to |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ---------- | ----------------- | ---------------------------------------------------------- |
| `status` | text | Current vitality, reputation, or dominance of the language |
| `spread` | links to Location | Geographical areas where the language is used or spoken |
| `dialects` | links to Language | Variants or dialect languages derived from the language |
# Law
> A Law represents a formalized rule or set of guidelines that governs the actions of individuals or groups within a specific jurisdiction.

A Law represents a formalized rule or set of guidelines that governs the actions of individuals or groups within a specific jurisdiction. Laws define what is permitted or prohibited, outline consequences for violations, and specify who interprets and enforces them. They can be administrative, punitive, or symbolic, and may carry legal, cultural, or spiritual significance.
Laws are formalized rules that govern behavior within a jurisdiction. They interact with:
* **Institutions** (which often issue them)
* **Locations and Zones** (which define where they apply)
* **Titles** (which define who upholds them or judges them)
* **Constructs** (which describe the abstract concepts they prohibit or rely on)
They are distinct from:
* **Constructs** (which describe the abstract concepts they prohibit or rely on)
* **Institutions** (which often issue them but are not rules themselves)
* **Titles** (which define authority but not the rules themselves)
[Law discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/law)
## Fields
[Section titled “Fields”](#fields)
### Code
[Section titled “Code”](#code)
| Field | Type | Description |
| ------------- | ------------------ | --------------------------------------------------------------- |
| `declaration` | text | The formal wording, expression, or decree of the law |
| `purpose` | text | The intent, motivation, or justification for the law’s creation |
| `date` | integer | Date the law was formally established, in world TIME units |
| `parent_law` | link to Law | A law that this law derives from, modifies, or enhances |
| `penalties` | links to Construct | Consequences intended to be applied when the law is contravened |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| -------------- | ------------------- | --------------------------------------------------------------------------------------- |
| `author` | link to Institution | The institution that created or issued the law |
| `locations` | links to Location | Locations where the law is supported or enforced |
| `zones` | links to Zone | Zones where the law is supported or enforced |
| `prohibitions` | links to Construct | Things that the law explicitly or effectively forbids |
| `adjudicators` | links to Title | Titles responsible for interpreting or ruling on the law’s application and jurisdiction |
| `enforcers` | links to Title | Titles responsible for enforcing or imposing the law |
# Location
> A Location represents a distinct place within the world where activities occur and elements converge.

A Location represents a distinct place within the world where activities occur and elements converge. Locations serve as anchors for other world elements, and describe how physical spaces are used, who organizes them, and how they relate to other places.
Locations are a structural backbone of your world. They interact with:
* **Collectives and Characters** (who inhabit or pass through them)
* **Institutions and Laws** (which govern or control them)
* **Objects and Titles** (which exist within or are tied to them)
* **Events and Narratives** (which often take place in or around them)
They are distinct from:
* **Zones** (which represent a specific marked area)
* **Institutions** (which organize power and policy within and towards them)
[Location discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/location)
## Fields
[Section titled “Fields”](#fields)
### Setting
[Section titled “Setting”](#setting)
| Field | Type | Description |
| ----------------- | ------------------- | ---------------------------------------------------------------------- |
| `form` | text | Visual and environmental aspects of the location |
| `function` | text | Main use, role, or purpose of the location within the world |
| `founding_date` | integer | Date on which the location was founded, established, or designated |
| `parent_location` | link to Location | Wider location that this location is part of |
| `populations` | links to Collective | Distinct collective groups or communities residing within the location |
### Politics
[Section titled “Politics”](#politics)
| Field | Type | Description |
| ------------------- | -------------------- | ------------------------------------------------------------------------------ |
| `political_climate` | text | Political structure, stability, and dynamics of the location |
| `primary_power` | link to Institution | Institution that has the highest degree of political control over the location |
| `governing_title` | link to Title | Governing figure assigned by the location’s primary power |
| `secondary_powers` | links to Institution | Institutions with significant political control |
| `zone` | link to Zone | Zone of interest that is associated with the location |
| `rival` | link to Location | Location with an active, traditional, or historical rivalry with this one |
| `partner` | link to Location | Location with active, cooperative, or historical ties to this one |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------ | ------------------ | ---------------------------------------------------------------------------- |
| `customs` | text | Cultural practices, habits, or festivals |
| `founders` | links to Character | Individual(s) who founded or named the location |
| `cults` | links to Construct | Significant religious constructs practiced or recognized at the location |
| `delicacies` | links to Species | Organisms or other species locally consumed or celebrated as specialty foods |
### Production
[Section titled “Production”](#production)
| Field | Type | Description |
| -------------------- | ------------------ | ----------------------------------------------------------- |
| `extraction_methods` | links to Construct | Techniques or strategies used to gather natural resources |
| `extraction_goods` | links to Construct | Products and materials that are gathered or obtained |
| `industry_methods` | links to Construct | Techniques or workflows used to refine or manufacture goods |
| `industry_goods` | links to Construct | Products and materials that are refined or manufactured |
### Commerce
[Section titled “Commerce”](#commerce)
| Field | Type | Description |
| -------------------- | ------------------ | ------------------------------------------------------------------------------------- |
| `infrastructure` | text | Roads, ports, and other physical systems that enable the movement of goods and people |
| `extraction_markets` | links to Location | Locations that receive extracted goods through trade, interchange, or seizure |
| `industry_markets` | links to Location | Locations that receive industrial goods through trade, interchange, or seizure |
| `currencies` | links to Construct | Trade media recognized or circulated at the location |
### Construction
[Section titled “Construction”](#construction)
| Field | Type | Description |
| ------------------ | ------------------ | --------------------------------------------------------------------------- |
| `architecture` | text | Look, form, and materials used in the built environment and location design |
| `buildings` | links to Object | Notable structural objects at the location |
| `building_methods` | links to Construct | Techniques or systems used to construct structures at the location |
### Defense
[Section titled “Defense”](#defense)
| Field | Type | Description |
| ------------------- | ------------------ | ---------------------------------------------------------------------------------------------------- |
| `defensibility` | text | Qualities of natural, constructed, and implemented defenses at the location |
| `elevation` | integer | Height or elevation of the location relative to surrounding terrain, defined in world DISTANCE units |
| `fighters` | links to Construct | Military units or forces responsible for defending the location |
| `defensive_objects` | links to Object | Objects or installations for defending the location |
# Map
> A Map represents a spatial template that defines a coordinate system for placing elements within your world.

A Map represents a spatial template that defines a coordinate system for placing elements within your world. Maps can embody a 2D or 3D object where Pins locate individual elements and Markers collectively define Zones. They can be nested hierarchically to represent different layers of a world or location.
Maps are the spatial foundation of your world. They interact with:
* **Pins** (which place individual elements at specific coordinates)
* **Markers** (which define Zone boundaries through coordinate sets)
* **Locations** (which Maps can represent spatially)
* **Other Maps** (through hierarchical parent-child relationships)
They are distinct from:
* **Locations** (which are named places with properties and relationships)
* **Zones** (which are meaningful areas defined by Markers on a Map)
[Map discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/map)
## Fields
[Section titled “Fields”](#fields)
### Details
[Section titled “Details”](#details)
| Field | Type | Description |
| ------------------ | ---------------- | --------------------------------------------------------------- |
| `background_color` | text | Color of the space around the map when zoomed out |
| `hierarchy` | integer | To associate or differentiate between maps with a common parent |
| `width` | integer | In pixels |
| `height` | integer | In pixels |
| `depth` | integer | In pixels |
| `parent_map` | link to Map | Map within which this map is contained |
| `location` | link to Location | Location element that this map represents |
# Marker
> A Marker is a special Map element.

A Marker is a special Map element. Groups of Markers, each at a specific coordinate, together designate a Zone in the world. Zones can be either lines or polygons (through supertype).
Markers are for painting the Zones of your world. They interact with:
* **Maps** (Markers exist on only one map at a time)
* **Zones** (A minimum of three Markers together defines one Zone)
They are distinct from:
* **Pins** (which locate an element at a specific coordinate on a Map)
[Marker discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/marker)
## Fields
[Section titled “Fields”](#fields)
### Details
[Section titled “Details”](#details)
| Field | Type | Description |
| ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `map` | link to Map | Map this marker is placed on |
| `zone` | link to Zone | Zone that is defined by this marker |
| `x` | integer | x coordinate, from bottom left of the map |
| `y` | integer | y coordinate, from bottom left of the map |
| `z` | integer | z coordinate, in case of depth |
| `order` | integer | Sequence position when markers define a polygon or line (0 = first point); without it, markers keep the order they were made in |
# Narrative
> Narratives represent stories told in your world, and can involve the organization or reinterpretation of Events.

Narratives represent stories told in your world, and can involve the organization or reinterpretation of Events.
Narratives are how stories are expressed in a world. They interact with:
* **Events** (the actual happenings they group or reinterpret)
* **Characters, Institutions, and Families** (as storytellers or subjects)
* **Objects, Constructs, and Laws** (as story elements or consequences)
* **Titles, Relations, and Collectives** (as sources of tension or resolution)
They are distinct from:
* **Events** (which record what happened without particular subjectivity)
* **Constructs** (which express abstract ideas but not narrative form)
* **Relations** (which may exist inside a story, but don’t organize one)
[Narrative discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/narrative)
## Fields
[Section titled “Fields”](#fields)
### Context
[Section titled “Context”](#context)
| Field | Type | Description |
| ------------------ | ------------------- | --------------------------------------------------------------- |
| `story` | text | Content of the narrative, as told or remembered |
| `consequences` | text | Outcomes or legacy of the narrative |
| `start_date` | integer | Date when the narrative begins, measured in world TIME units |
| `end_date` | integer | Date when the narrative ends, measured in world TIME units |
| `order` | integer | Position of this narrative within a parent narrative’s sequence |
| `parent_narrative` | link to Narrative | Larger narrative that this narrative takes place in |
| `protagonist` | link to Character | Primary character of the narrative |
| `antagonist` | link to Character | Opposing character of the narrative |
| `narrator` | link to Character | Character credited with telling or recording the narrative |
| `conservator` | link to Institution | Institution that preserves or curates the narrative |
### Involves
[Section titled “Involves”](#involves)
| Field | Type | Description |
| -------------- | -------------------- | --------------------------------------- |
| `events` | links to Event | Events relevant to the narrative |
| `characters` | links to Character | Characters relevant to the narrative |
| `objects` | links to Object | Objects relevant to the narrative |
| `locations` | links to Location | Locations relevant to the narrative |
| `species` | links to Species | Species relevant to the narrative |
| `creatures` | links to Creature | Creatures relevant to the narrative |
| `institutions` | links to Institution | Institutions relevant to the narrative |
| `traits` | links to Trait | Traits relevant to the narrative |
| `collectives` | links to Collective | Groups relevant to the narrative |
| `zones` | links to Zone | Zones relevant to the narrative |
| `abilities` | links to Ability | Abilities relevant to the narrative |
| `phenomena` | links to Phenomenon | Phenomena relevant to the narrative |
| `languages` | links to Language | Languages relevant to the narrative |
| `families` | links to Family | Families relevant to the narrative |
| `relations` | links to Relation | Relationships relevant to the narrative |
| `titles` | links to Title | Titles relevant to the narrative |
| `constructs` | links to Construct | Constructs relevant to the narrative |
| `laws` | links to Law | Laws relevant to the narrative |
# Object
> Objects are tangible, non-living things that can be made, owned, traded or destroyed.

Objects are tangible, non-living things that can be made, owned, traded or destroyed. They can enable abilities, consume resources, trigger phenomena and shape stories.
Objects are a core material element of worlds. They interact with:
* **Characters** (who own or interact with them)
* **Traits and Abilities** (which determine who can use them and how)
* **Locations** (which define where they are stored or used)
* **Phenomena** (which they may emit, trigger, or be affected by)
They are distinct from:
* **Constructs** (which define their underlying principles or technologies)
* **Creatures and Characters** (which are animate)
* **Phenomena** (which represent ongoing effects or supernatural features)
[Object discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/object)
## Fields
[Section titled “Fields”](#fields)
### Form
[Section titled “Form”](#form)
| Field | Type | Description |
| --------------- | ------------------ | -------------------------------------------------------------------- |
| `aesthetics` | text | Appearance, design, or visual presentation of the object |
| `weight` | integer | Approximate or exact mass of the object, defined by world MASS units |
| `amount` | integer | The number of identical units in this object entry |
| `parent_object` | link to Object | Larger object that this one is part of or contained within |
| `materials` | links to Construct | The physical matter that constitutes the object |
| `technology` | links to Construct | Mechanisms relating to the object’s design or operation |
### Function
[Section titled “Function”](#function)
| Field | Type | Description |
| ----------- | ------------------- | -------------------------------------------------------- |
| `utility` | text | Intended purpose or primary use of the object |
| `effects` | links to Phenomenon | Phenomena potentially triggered or emitted on object use |
| `abilities` | links to Ability | Abilities that the object grants or enables |
| `consumes` | links to Construct | What might be used or depleted on object use |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------ | ---------------- | --------------------------------------------------------------------------- |
| `origins` | text | Background or history of the object |
| `location` | link to Location | Physical place where the object is currently located or stored |
| `language` | link to Language | Required to read, understand, or activate the object |
| `affinities` | links to Trait | Traits that resonate with or enhance the object’s use, function, or effects |
# Phenomenon
> Phenomena are ongoing or emergent conditions that act in or upon the world.

Phenomena are ongoing or emergent conditions that act in or upon the world. They are not defined by intent or agency, but might be wielded or enabled by other elements.
Phenomena interact with the rest of the world in some meaningful ways, including:
* **Characters** (wielding or enabling)
* **Traits and Abilities** (enhancing or being enhanced)
* **Objects and Constructs** (trigger or affected)
* **Locations** (appear or enrich)
They are distinct from:
* **Events** (which are dated incidents of history)
* **Constructs** (which are designed or conceptual structures)
* **Abilities** (which are agent-bound)
[Phenomenon discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/phenomenon)
## Fields
[Section titled “Fields”](#fields)
### Mechanics
[Section titled “Mechanics”](#mechanics)
| Field | Type | Description |
| -------------- | ---------------- | ------------------------------------------------------------------------------------- |
| `expression` | text | How the phenomenon manifests or takes shape in the world |
| `effects` | text | The primary outcomes or changes caused by the phenomenon |
| `duration` | integer | The amount of time the phenomenon lasts, measured in world TIME units |
| `catalysts` | links to Object | Objects or materials that initiate or enhance the phenomenon |
| `empowerments` | links to Ability | Abilities that initiate or enhance the phenomenon, or are initiated or enhanced by it |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| -------------- | ------------------ | --------------------------------------------------------------------------- |
| `mythology` | text | Cultural, religious, or narrative meaning associated with the phenomenon |
| `system` | link to Phenomenon | Broader phenomenon that this one is part of or linked to |
| `triggers` | links to Construct | Conceptual mechanisms or patterns that cause the phenomenon to activate |
| `wielders` | links to Character | Characters capable of intentionally directing or controlling the phenomenon |
| `environments` | links to Location | Locations where the phenomenon occurs or is known to manifest |
# Pin
> A Pin is a special Map element.

A Pin is a special Map element. Pins represent a single element on a single Map, indicating its position in that particular world view.
Pins place elements on a map. They interact with:
* **Maps** (Pins exist on only one map at a time)
* **Elements** (Pins locate a single element on a Map)
They are distinct from:
* **Markers** (which define a Zone on a Map)
[Pin discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/pin)
## Fields
[Section titled “Fields”](#fields)
### Details
[Section titled “Details”](#details)
| Field | Type | Description |
| -------------- | ------------------------------ | --------------------------------------------- |
| `map` | link to Map | Map that the pin is placed on |
| `element_type` | text: one of the 22 type names | The type of the linked element, any of the 22 |
| `element_id` | id of an element of that type | The id of the linked element |
| `x` | integer | x coordinate, from bottom left of the map |
| `y` | integer | y coordinate, from bottom left of the map |
| `z` | integer | z coordinate, in case of depth (optional) |
# Relation
> Relations are non-material and capture meaningful connections between world elements.

Relations are non-material and capture meaningful connections between world elements. They track interactions, alignments, conflicts, or agreements between characters, groups, places, and other entities.
Relations are special definitions between world elements. They interact with:
* **Characters and Institutions** (defining personal and organizational relationships)
* **Events** (arising from or recognizing a relationship)
* **Objects, Zones, Traits, and Abilities** (as things exchanged, contested, or shared)
They are distinct from:
* **Titles** (which indicate formal authority, not interpersonal context)
* **Events** (which define a point in time, not a social span)
* **Constructs** (which encode rules or structures, not associations between elements)
* **Families and Collectives** (which describe structure, not necessarily activity)
[Relation discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/relation)
## Fields
[Section titled “Fields”](#fields)
### Nature
[Section titled “Nature”](#nature)
| Field | Type | Description |
| ------------ | ----------------- | ---------------------------------------------------------------- |
| `background` | text | History and origin of the relation |
| `start_date` | integer | Date when the relation began, defined in world TIME units |
| `end_date` | integer | Date when the relation ended if any, defined in world TIME units |
| `intensity` | integer | Significance of the relation, on a relative scale of 0 to 100 |
| `actor` | link to Character | Primary character defining the relation |
| `events` | links to Event | Events where the relation is involved or relevant |
### Involves
[Section titled “Involves”](#involves)
| Field | Type | Description |
| -------------- | -------------------- | ----------------------------------------------------------- |
| `characters` | links to Character | Characters relevant to the relation |
| `objects` | links to Object | Objects relevant to the relation |
| `locations` | links to Location | Locations relevant to the relation |
| `species` | links to Species | Species relevant to the relation |
| `creatures` | links to Creature | Creatures relevant to the relation |
| `institutions` | links to Institution | Institutions relevant to the relation |
| `traits` | links to Trait | Traits relevant to the relation |
| `collectives` | links to Collective | Collectives relevant to the relation |
| `zones` | links to Zone | Zones relevant to the relation |
| `abilities` | links to Ability | Abilities relevant to the relation |
| `phenomena` | links to Phenomenon | Phenomena relevant to the relation |
| `languages` | links to Language | Languages relevant to the relation |
| `families` | links to Family | Families relevant to the relation |
| `titles` | links to Title | Titles relevant to the relation |
| `constructs` | links to Construct | Concepts, contracts, or principles relevant to the relation |
| `narratives` | links to Narrative | Narratives relevant to the relation |
# Species
> A Species defines a distinct biological or cultural form of life.

A Species defines a distinct biological or cultural form of life. They help define how life functions or diverges across environments and histories.
Species shape physical and behavioral defaults of a world. They interact with:
* **Characters and Creatures** (defined as members of species)
* **Locations and Zones** (determining habitat and environmental interaction)
* **Traits and Constructs** (expressing shared abilities or mythic significance)
They are distinct from:
* **Characters** (individual agents with goals and identity)
* **Creatures** (instinct-driven individuals)
* **Collectives** (behavioral groups of individuals)
[Species discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/species)
## Fields
[Section titled “Fields”](#fields)
### Biology
[Section titled “Biology”](#biology)
| Field | Type | Description |
| -------------- | ------------------ | -------------------------------------------------------------------------------- |
| `appearance` | text | Typical physical or form features of the species |
| `life_span` | integer | Average or typical life expectancy of an individual, defined in world TIME units |
| `weight` | integer | Average or typical adult weight, defined in world MASS units |
| `nourishment` | links to Species | Other species consumed as food sources |
| `reproduction` | links to Construct | Reproductive method(s) of the species |
| `adaptations` | links to Ability | Special physiological or evolutionary abilities |
### Psychology
[Section titled “Psychology”](#psychology)
| Field | Type | Description |
| --------------- | -------------- | ----------------------------------------------------------- |
| `instincts` | text | Innate behavioral drives and survival tendencies |
| `sociality` | text | Typical patterns of social behavior |
| `temperament` | text | Overall behavioral disposition |
| `communication` | text | Typical methods and approaches of interaction |
| `aggression` | integer | General aggressiveness level, on relative scale of 0 to 100 |
| `traits` | links to Trait | Behavioral patterns associated with the species |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ---------------- | ------------------- | --------------------------------------------------------- |
| `role` | text | The species’ ecological or cultural function in the world |
| `parent_species` | link to Species | Species that the species is considered a subspecies of |
| `locations` | links to Location | Locations associated with the species or its habitat |
| `zones` | links to Zone | Zones associated with the species or its habitat |
| `affinities` | links to Phenomenon | Phenomena associated with the species or its behavior |
# Title
> A Title is a formal designation that confers identity, standing, or power within a world.

A Title is a formal designation that confers identity, standing, or power within a world. It may be granted, inherited, or assumed, and often functions within a wider system of governance, belief, or custom. Titles help structure how individuals relate to institutions, spaces, and ideas across time.
Titles define structured identity and power. They interact with:
* **Characters** (holding titles)
* **Institutions** (issuing or hosting titles)
* **Zones, Locations, Objects** (governing or representing)
* **Constructs, Laws, and Collectives** (giving them shape or purpose)
They are distinct from:
* **Traits** (which describe personal attributes)
* **Abilities** (which define actions that can physically be taken)
* **Relations** (which describe personal or emotional bonds)
[Title discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/title)
## Fields
[Section titled “Fields”](#fields)
### Mandate
[Section titled “Mandate”](#mandate)
| Field | Type | Description |
| ---------------- | ------------------- | ------------------------------------------------------------------------- |
| `authority` | text | Rights or powers granted by the title |
| `eligibility` | text | Conditions or qualifications for receiving or holding the title |
| `grant_date` | integer | Date on which the title was granted, defined in world TIME units |
| `revoke_date` | integer | Date on which the title ended or was revoked, defined in world TIME units |
| `issuer` | link to Institution | Institution that formally created or granted the title |
| `body` | link to Institution | Institution in which the title functions or holds relevance |
| `superior_title` | link to Title | Another title that has authority over this one |
| `holders` | links to Character | Characters who currently hold or represent the title |
| `symbols` | links to Object | Objects that symbolize or authorize the title |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| -------------- | -------------------- | ------------------------------------------------------------------------ |
| `status` | text | Current state or general condition of the title |
| `history` | text | Background information on the title’s origin, evolution, or significance |
| `characters` | links to Character | Characters otherwise relevant to the title |
| `institutions` | links to Institution | Institutions relevant to the title |
| `families` | links to Family | Families relevant to the title |
| `zones` | links to Zone | Zones relevant to the title |
| `locations` | links to Location | Locations relevant to the title |
| `objects` | links to Object | Objects otherwise relevant to the title |
| `constructs` | links to Construct | Constructs relevant to the title |
| `laws` | links to Law | Laws relevant to the title |
| `collectives` | links to Collective | Collectives relevant to the title |
| `creatures` | links to Creature | Creatures relevant to the title |
| `phenomena` | links to Phenomenon | Phenomena relevant to the title |
| `species` | links to Species | Species relevant to the title |
| `languages` | links to Language | Languages relevant to the title |
# Trait
> Traits describe qualities that shape how a character or creature acts, responds, or is perceived.

Traits describe qualities that shape how a character or creature acts, responds, or is perceived. These are not active powers or learned abilities, but underlying aspects of identity.
Traits enrich the make up of actors. They interact with:
* **Characters** (holding traits and being shaped by them)
* **Abilities** (requiring, unlocking, or enhancing traits)
* **Species** (establishing cultural or biological expressions)
They are distinct from:
* **Constructs** (which are not directly linked to actors)
* **Titles** (which are formally constructed, not potentially natural)
[Trait discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/trait)
## Fields
[Section titled “Fields”](#fields)
### Qualitative
[Section titled “Qualitative”](#qualitative)
| Field | Type | Description |
| --------------------- | ---- | --------------------------------------------------------------------- |
| `social_effects` | text | Relating to social relationships, reputation, or interaction dynamics |
| `physical_effects` | text | Relating to physical changes, limitations, or enhancements |
| `functional_effects` | text | Relating to practical or learned performance or aptitude |
| `personality_effects` | text | Relating to temperament, mental state, or personality expression |
| `behaviour_effects` | text | Relating to visible aspects and patterns of behavior |
### Quantitative
[Section titled “Quantitative”](#quantitative)
| Field | Type | Description |
| ------------ | ------- | ---------------------------------------- |
| `charisma` | integer | Affecting a character’s charisma score |
| `coercion` | integer | Affecting a character’s coercion score |
| `competence` | integer | Affecting a character’s competence score |
| `compassion` | integer | Affecting a character’s compassion score |
| `creativity` | integer | Affecting a character’s creativity score |
| `courage` | integer | Affecting a character’s courage score |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| --------------------- | ---------------- | -------------------------------------------------------------- |
| `significance` | text | Describes the trait’s societal, symbolic, or systemic presence |
| `anti_trait` | link to Trait | Opposing trait that contradicts or nullifies the trait |
| `empowered_abilities` | links to Ability | Abilities strengthened or enabled by the trait |
# Zone
> Zones represent abstract or meaningful areas within the world that hold significance due to cultural, political, environmental, or narrative reasons.

Zones represent abstract or meaningful areas within the world that hold significance due to cultural, political, environmental, or narrative reasons. They are not defined by geometry themselves but gain spatial presence through linked map markers. Zones can be used to track boundaries, effects, or jurisdictions that extend beyond a single location.
Zones are spatial definitions within a world. They interact with:
* **Maps** (that a zone exists on)
* **Institutions** (which define or claim zones)
* **Creatures** (that dominate or traverse them)
* **Phenomena** (that enrich or transform the area)
* **Titles** (for roles tied to management or protection)
They are distinct from:
* **Locations** (which are specific places of interest or activity)
* **Markers** (which define the geometry of a zone on a map)
[Zone discussions on GitHub](https://github.com/OnlyWorlds/OnlyWorlds/discussions/categories/zone)
## Fields
[Section titled “Fields”](#fields)
### Scope
[Section titled “Scope”](#scope)
| Field | Type | Description |
| -------------- | ------------------- | ----------------------------------------------------------------------------------- |
| `role` | text | The operational function or intent of the zone |
| `start_date` | integer | Date when the zone becomes extant or relevant, defined in world TIME units |
| `end_date` | integer | Date when the zone ceases to be meaningful or enforced, defined in world TIME units |
| `phenomena` | links to Phenomenon | Phenomena that affect, define, or occur within the zone |
| `linked_zones` | links to Zone | Other zones that are associated with the zone |
### World
[Section titled “World”](#world)
| Field | Type | Description |
| ------------- | ------------------- | ------------------------------------------------------------------ |
| `context` | text | Historical and key knowledge about the zone |
| `populations` | links to Collective | Distinct collective groups or communities residing within the zone |
| `titles` | links to Title | Titles assigned to represent, manage, or protect the zone |
| `principles` | links to Construct | Influential mechanics acted within, upon, or by the zone |
# A Worked Example
> One small world, Moppetopia, read through the API to show how the categories, links and timeline fit together.
Moppetopia is a demo world: a civilization of felt creatures called moppets, a navy that fights in puddles and in space, and an old war its people tell in different ways. Anyone can read it with its demo key `0000000001` (read-only, no PIN). Every call on this page runs as written.
```bash
curl -s "https://www.onlyworlds.com/api/v2/world" -H "API-Key: 0000000001"
```
## One Element
[Section titled “One Element”](#one-element)
Admiral Fluffington is a Character: an individual with agency. His text fields (`description`, `physicality`, `mentality`, `background`) hold prose. His link fields hold the ids of other elements.
```bash
curl -s "https://www.onlyworlds.com/api/v2/character?name__icontains=fluffington" -H "API-Key: 0000000001"
```
The answer is a page of results, trimmed here to the link fields:
```json
{
"data": [{
"id": "0695db83-afd7-76ee-8000-00f4295f8866",
"name": "Admiral Fluffington",
"birth_date": 255,
"location": "0695db83-972a-74da-8000-262f1559a7f1",
"species": ["0695db8a-1642-7449-8000-c896529a4537", "0698c9df-422f-7255-8000-624e6f5e0a47"],
"family": ["069945d9-d873-7076-8000-96d538f41a22"],
"rivals": ["0695db83-b69a-772b-8000-557e9ba0dcc3"]
}],
"has_more": false,
"next_cursor": null
}
```
`location` is a single link: one Location. `species`, `family` and `rivals` are multi links: lists. Fetch him by his `id` and add `?expand=` to read the names behind the ids:
```bash
curl -s "https://www.onlyworlds.com/api/v2/character/0695db83-afd7-76ee-8000-00f4295f8866?expand=location,family,rivals&fields=name,location,family,rivals" -H "API-Key: 0000000001"
```
He is in Feltropolis, an orbital station; his family is House Stuffington, a naval dynasty; his rival is Captain Snoot.
## Links Point One Way
[Section titled “Links Point One Way”](#links-point-one-way)
A Character has no field for titles. The link lives on the Title: Admiral is a Title whose `holders` are three Characters and whose `body` is the Puddle Navy, an Institution.
```bash
curl -s "https://www.onlyworlds.com/api/v2/title?name=Admiral&expand=holders,body&fields=name,body,holders" -H "API-Key: 0000000001"
```
So to find what a Character holds, read the Titles and check their `holders`: there is no reverse lookup, and a filter on a link field is a `422` ([Filters](/docs/development/api/reads#filters)). Each link sits on the element that holds the field; the element pages ([Title](/docs/schema/element_categories/title), [Character](/docs/schema/element_categories/character)) show which side that is.
## Places Within Places
[Section titled “Places Within Places”](#places-within-places)
Locations nest through `parent_location`, the wider Location a place is part of. Feltropolis has seven levels, each its own Location whose parent is Feltropolis; the places on a level name that level as theirs, so The Dry Room and Whisper Gallery sit under Level 3, The Squeeze. A tool can draw the station as a tree from that one field.
## Same Name, Two Categories
[Section titled “Same Name, Two Categories”](#same-name-two-categories)
The Treaty of Soft Landings ended the war, and Moppetopia holds it twice. As a **Law** it is the document: its `declaration` holds the wording, its `purpose` why it was made. As an **Event** it is the signing: a `start_date`, the Event that triggered it (The Great Puddle War), and the Locations where it happened. The name is shared; what each element can carry is set by its category.
## One Event, Two Stories
[Section titled “One Event, Two Stories”](#one-event-two-stories)
The Moistened Valley Massacre is an Event: a time-bound happening, with `triggers` (the Events that led to it), `consequences` (a text field) and the Characters involved. Three of the world’s Narratives tell it:
| Narrative | Told by | Events |
| ---------------------------- | --------------------------------- | ------------------------------------------------------------------------ |
| The Moistened Valley Account | `conservator`: the Treaty Council | The Great Puddle War, Treaty of Soft Landings, Moistened Valley Massacre |
| The Ballad of Splashworth | `conservator`: the Puddle Navy | Moistened Valley Massacre |
| Snoot’s Counter-History | `narrator`: Captain Snoot | Moistened Valley Massacre, The Incident |
```bash
curl -s "https://www.onlyworlds.com/api/v2/narrative?expand=narrator,conservator,events&fields=name,narrator,conservator,events" -H "API-Key: 0000000001"
```
The call lists all of the world’s Narratives, these three among them. The Event records what happened; each Narrative is one account of it, with a teller or a keeper and its own selection of Events.
## A Connection With Its Own History
[Section titled “A Connection With Its Own History”](#a-connection-with-its-own-history)
Fluffington’s `rivals` field says he opposes Snoot, and nothing more. The Fluffington-Snoot Rivalry is a Relation: it has an `actor` (Fluffington), both Characters, an `intensity` of 85 on a scale of 0 to 100, a `start_date`, a Location (the Naval Academy), and a `background` explaining why. Use a plain link for the fact of a connection; use a Relation when the connection itself has a story. [Conventions](/docs/schema/conventions) covers the choice.
## Time
[Section titled “Time”](#time)
Dates are integers on the world’s own timeline. Moppetopia counts in years (`time_basic_unit`), from 0 to 500, and its present is 500 (`time_range_current`). The massacre’s `start_date` is 248, the signing’s 250, Fluffington’s `birth_date` 255. [Worlds](/docs/schema/worlds) covers the timeline fields.
## What It Leaves Out
[Section titled “What It Leaves Out”](#what-it-leaves-out)
Moppetopia uses 18 of the 22 categories: it has no Creatures, and its one Map has no Pins, Markers or Zones yet. Most of its elements leave most fields empty, and the world is still valid: [Fields](/docs/schema/fields) lists what is required.
# Fields
> The base fields every element carries, the field types, what is required, and extension fields.
Every element carries the same base fields, then the fields of its own category. Category fields are listed on each category’s page, for example [Character](/docs/schema/element_categories/character).
## Base Fields
[Section titled “Base Fields”](#base-fields)
| Field | Type | Required | Description |
| ------------- | ------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `id` | string (uuid) | No (server-assigned if omitted) | Unique identifier, UUIDv7 |
| `name` | string | Yes: the key must be present; an empty string is accepted | Display name, up to 255 characters |
| `world` | string (uuid) | No (set by the API key; never sent in a body) | The world this element belongs to |
| `description` | string | No | Any kind of details about the element |
| `supertype` | string | No | The top-level category the element belongs to in its world, up to 128 characters |
| `subtype` | string | No | A further classification within the supertype, up to 128 characters |
| `image_url` | string (url) | No | Link to a representative image, up to 1024 characters |
Ids are UUIDv7 when the server mints them, which makes them time-sortable. A client may supply its own `id` on create: any RFC 4122 UUID is accepted.
Supertype and subtype are free text, so each world defines its own. [Conventions](/docs/schema/conventions) covers how to use them.
### Server-Kept Fields
[Section titled “Server-Kept Fields”](#server-kept-fields)
The API adds these to every element it returns. They are platform bookkeeping, not part of the standard.
| Field | Description |
| -------------------------- | ---------------------------------------------------------------------------------- |
| `type` | The element’s category, as a lowercase slug (`character`), first key in every body |
| `created_at`, `updated_at` | Timestamps |
| `change_seq` | The world’s change sequence at the element’s last write |
| `created_by` | The world membership that created the element, or null when the world’s owner did |
A write body that carries `type`, `created_at`, `updated_at` or `change_seq` is rejected with `422`. `world` and `created_by` are tolerated and ignored.
## Field Types
[Section titled “Field Types”](#field-types)
| Type | What it holds | On the API | Example |
| ------------ | ------------------------------------------ | ------------------------------------------- | ------------------------------------- |
| text | A string | `"…"`; empty is `""` | A Character’s `background` |
| number | A whole number (integer); some are bounded | an integer or `null` | A Character’s `height` |
| single link | One element of a named category | a uuid or `null` | A Character’s `location` (a Location) |
| multi link | Any number of elements of a named category | an array of uuids; empty is `[]` | A Character’s `friends` (Characters) |
| generic link | One element of any category | two fields, `element_type` and `element_id` | A Pin’s `element` |
| array | A list of plain values | a JSON array | A world’s `time_format_names` |
Link fields use their bare names in both directions: `location`, `friends`. There is no `_id` or `_ids` suffix on the v2 API.
**Bounded numbers.** Most numbers are open. These are bounded:
| Range | Fields |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 to 100 | Character `charisma`, `coercion`, `competence`, `compassion`, `creativity`, `courage` · Ability `potency` · Relation `intensity` · Species `aggression` |
| -100 to 100 | Trait `charisma`, `coercion`, `competence`, `compassion`, `creativity`, `courage` |
Numbers are integers. The API truncates a decimal instead of rejecting it (`7.9` is stored as `7`), so scale values to whole units before writing.
**Units.** Dates are integers in the world’s time units (see [Worlds](/docs/schema/worlds)). Some measurements name their unit kind in the schema: a Character’s `height` uses the world’s length units, `weight` its mass units.
## What Is Required
[Section titled “What Is Required”](#what-is-required)
On every element, only `name` is required: the key must be present on create and full replace, and an empty string is accepted. Markers, which mark the boundary points of a Zone, are often unnamed.
That includes the map categories. A [Pin](/docs/schema/element_categories/pin) or [Marker](/docs/schema/element_categories/marker) without a map or a position is valid, so an app can keep pins that are not on any map yet. Every other field accepts `null`.
Coordinates `x` and `y` are integers measured from the bottom left of the map; `z` is optional, for depth. A Marker’s `order` is its position in the sequence when Markers define a polygon or line (`0` is the first point); without it, Markers keep the order they were made in.
## Extension Fields
[Section titled “Extension Fields”](#extension-fields)
Fields whose names start with a reserved prefix are accepted on write, stored as they are, and returned verbatim:
| Prefix | For |
| ---------- | ------------------------------------------ |
| `x_*` | The open namespace, for any tool or person |
| `atlas_*` | Atlas |
| `shadow_*` | The world folder format |
An extension field can hold any JSON value. The server does not validate, index or filter extensions. Each element can carry at most 64 KB of them, counted as compact UTF-8 JSON. A `PATCH` merges extension fields by key.
Any other unknown field is rejected with `422` naming the field, so a typo such as `freinds` fails loudly instead of being stored.
The base schema is what makes a world portable between tools. Data held in extension fields is read only by the tools that know those fields.
# World Folders
> The folder format a world can live in on disk, and the rules a tool follows to read and write one.
A world folder is one OnlyWorlds world stored as plain JSON files: one file for the world, one per element. A text editor opens it, a copy backs it up, and any tool reads it with no account, key or network. Under git, each commit is a point in the world’s history and a branch is a what-if; element ids stay the same on every branch.
## Layout
[Section titled “Layout”](#layout)
```text
moppetopia/
world.json the world
elements/
character/
admiral-fluffington--295f8866.json
location/
... one folder per category present, lowercase singular
media/ optional; its format is not yet specified
.atlas/ a tool's private state; never needed to read the world
```
A folder holds one world. All 22 categories, `map`, `pin`, `zone` and `marker` included, live under `elements//`. A missing category folder means no elements of that category.
The `id` inside a file is the element’s identity. The filename is for people: rename a file and nothing breaks. The recommended name is `--.json`. The slug is the name in lowercase ASCII, each run of other characters as one hyphen, cut to 40 characters, any trailing hyphen removed. The tail is the id’s last 8 characters. An element with no usable name gets `.json`.
## world.json
[Section titled “world.json”](#worldjson)
Two keys are required: `id` (a non-empty string) and `name` (a string, which may be empty). The rest are optional.
| Key | Holds |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `description`, `image_url`, timeline fields | As on [Worlds](/docs/schema/worlds). The current time is `time_current` on disk, the standard’s name; the API calls it `time_range_current`. |
| `format_version` | The version of this format the folder was written against |
| `api` | The link to a world on onlyworlds.com (`world_id`, `api_key`). Present: the folder syncs with it. Absent: a local world. |
| `snapshot_*`, `writable` | Only on a [snapshot](#snapshots) |
```json
{
"id": "0695db52-5433-7332-8000-db7fe10da375",
"name": "Moppetopia",
"time_basic_unit": "Year",
"time_range_min": 0,
"time_range_max": 500,
"time_current": 500
}
```
## Element Files
[Section titled “Element Files”](#element-files)
An element file must hold a non-empty string `id`. The rest are the category’s [fields](/docs/schema/fields), link fields under bare names holding an id or a list of ids, as in the API. Trimmed:
```json
{
"id": "0695db83-afd7-76ee-8000-00f4295f8866",
"name": "Admiral Fluffington",
"location": "0695db83-972a-74da-8000-262f1559a7f1",
"rivals": ["0695db83-b69a-772b-8000-557e9ba0dcc3"]
}
```
A reader fills in `type` from the folder name unless the file declares one. A file without an `id` is skipped and left as it is. Tools may add their own fields under a prefix (`atlas_*`, `x_*`). An element named in prose is `[Label](ow:///)`. Map coordinates run from the bottom left, y upward.
## Folders and onlyworlds.com
[Section titled “Folders and onlyworlds.com”](#folders-and-onlyworldscom)
* **Linked**: a folder with an `api` block is that account world on disk. To find the server’s world, read `api.world_id` first and fall back to `id` (older folders can differ).
* **Account to folder**: let [Atlas](/docs/tools/atlas) download the world (a linked folder), or by script read [changes](/docs/development/api/changes) from the start until `has_more` is false and write `world.json` from `GET /api/v2/world`, renaming `time_range_current` to `time_current`.
* **Folder to account**: Atlas can take a local folder online. Or send the files to an empty world through [`/bulk`](/docs/development/api/writes#bulk), stripping `local_updated_at`, `server_updated_at`, `image_media_id`, `created_at`, `updated_at`, `created_by`, `type` and `change_seq`, and renaming `map_id` to `map`, `zone_id` to `zone`.
* **A copy to share** may drop the `api` block, which holds a key, and nothing else. It keeps its `id`. If a key was ever pushed somewhere public, revoke it in your account.
* **Archives**: one world per zip, as one top-level folder; readers also accept `world.json` at the root.
## Snapshots
[Section titled “Snapshots”](#snapshots)
A snapshot is a frozen copy of a world at one moment, such as a chapter’s end.
| Key | Holds |
| --------------------- | ------------------------------------------------------------------------- |
| `snapshot_of` | The source world’s id |
| `snapshot_label` | A label, such as `"ch05"` |
| `snapshot_at` | Capture time (ISO 8601) |
| `snapshot_change_seq` | The source’s change cursor at capture |
| `snapshot_counts` | Optional: files written per category. A receipt; the files are the truth. |
| `snapshot_torn` | `true` only on a capture known to be inconsistent |
| `writable` | `false`. Advisory; no tool is known to honor it. |
The writer mints a fresh world id and keeps every element id; a re-capture under the same label keeps the snapshot’s id. It copies `name` exactly and every `world.json` key the source had, keeps `change_seq`, `created_at`, `updated_at` and `type` as received, and writes no `./` folder. If the change cursor moved during the walk it fails, or on an explicit override sets `snapshot_torn: true`.
## Who Reads and Writes Folders
[Section titled “Who Reads and Writes Folders”](#who-reads-and-writes-folders)
| Tool | With folders |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Atlas](/docs/tools/atlas) | Reference implementation; stores every world as a folder. Folder mode needs a Chromium browser. Worlds sit inside `onlyworlds-atlas/-/`: point other tools at the world’s own folder. |
| [Obsidian plugin](/docs/tools/obsidian-plugin) | Exports a world as a folder; imports a folder into notes |
| [Toolkit](/docs/development/toolkit) | Reads a folder in place of the API; parsing writes new ones |
| [Azgaar converter](https://github.com/OnlyWorlds/azgaar-converter) | Azgaar map in, as a folder |
| [Foundry converter](https://github.com/OnlyWorlds/foundry-converter) | Folder out, to Foundry VTT |
## Rules for Tools
[Section titled “Rules for Tools”](#rules-for-tools)
**Reading**
* MUST read every `*.json` in a category folder and key on the inner `id`; MUST NOT parse filenames.
* MUST treat a missing category folder as empty, and MUST NOT require any `./` folder.
* SHOULD also read `spatial/{map,pin,zone,marker}/` (older Atlas).
* Accept both spellings: `map`/`map_id`, `zone`/`zone_id`, `time_current`/`time_range_current`.
* MUST NOT rewrite a file’s key spellings as a side effect of opening it.
* A tool holding several worlds MUST key storage by source and world id, never world id alone.
**Writing**
* UTF-8 without BOM, LF endings, 2-space indent, one trailing newline, keys in received order (never sorted), `1` not `1.0`.
* Write under `elements//`, never `spatial/`; SHOULD omit empty category folders.
* Two elements with the same filename: MUST use `.json`, ties broken by ascending id.
* An id, once minted, MUST be kept and MUST NOT be re-derived (from a new name, say).
* MUST NOT invent data it lacks, such as timestamps, nor drop data it has.
* MUST preserve unknown fields and MUST NOT change another tool’s prefixed field at any depth: copy it through byte for byte.
* `format_version` only when creating a folder. MUST NOT create or edit a `.gitignore` in a folder it did not create (in your own repo, `.*/` keeps every tool folder out).
* One writer per folder at a time; never write into another tool’s `./`.
* In a linked folder, every changed element file MUST get `local_updated_at` set to now (UTC, ISO 8601), `server_updated_at` left alone; otherwise Atlas never sends the edit. New files carry neither.
# Worlds
> The world container, its fields, and how it configures a world's timeline.
A world is the top-level container for elements, representing a complete setting at any scope you define. Every element belongs to exactly one world, and a world key reads or writes exactly one world.
## Core
[Section titled “Core”](#core)
| Field | Type | Required | Description |
| ------------- | ------------- | -------------------- | ------------------------------------ |
| `id` | string (uuid) | No (server-assigned) | Unique world identifier (UUIDv7) |
| `name` | string | Yes | Display name; it cannot be empty |
| `description` | string | No | Text description |
| `image_url` | string (url) | No | Cover image or representative visual |
## Timeline
[Section titled “Timeline”](#timeline)
| Field | Type | Required | Description |
| ------------------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `time_format_names` | array of strings | No | Names of each time step: `["Day", "Week", "Month", "Year"]`, or custom names like `["Sol", "Cycle", "Season", "Era"]` |
| `time_format_equivalents` | array of strings | No | Basic units per step, e.g. `["1", "7", "30", "365"]` |
| `time_basic_unit` | string | No | Smallest time unit (e.g. `"Day"` or `"Hour"`) |
| `time_range_min` | integer | No | Earliest tracked time point |
| `time_range_max` | integer | No | Latest tracked time point |
| `time_range_current` | integer | No | Current time in your world. The standard’s YAML names this field `time_current`. |
Element fields that hold a moment, such as a Character’s `birth_date` or an Event’s `start_date`, are integers in the world’s time units.
### Timeline Examples
[Section titled “Timeline Examples”](#timeline-examples)
**Fantasy world with a custom calendar:**
* `time_format_names`: `["Sun", "Tenday", "Moon", "Turning"]`
* `time_basic_unit`: `"Sun"`
* `time_range_current`: `1247` (year 1247 of the Third Age)
**Science fiction setting with stardates:**
* `time_format_names`: `["Cycle", "Rotation", "Orbit", "Epoch"]`
* `time_basic_unit`: `"Cycle"`
* `time_range_min`: `0`
* `time_range_max`: `128256`
Timeline fields work with [Events](/docs/schema/element_categories/event) and [Narratives](/docs/schema/element_categories/narrative).
## Platform Fields
[Section titled “Platform Fields”](#platform-fields)
onlyworlds.com adds fields of its own to the world it serves. They are part of the platform, not the standard.
| Field | Type | Description |
| ----------------- | --------------------- | ------------------------------------------------------------------------- |
| `public_read` | boolean | Whether the world is open to read |
| `owner_character` | string (uuid) or null | The owner’s own Character in this world (“this Character is me”), or null |
| `created_at` | string (date-time) | When the world was created |
| `updated_at` | string (date-time) | When the world last changed |
The world named by a key is `GET /api/v2/world/`. Only the world’s owner can change it, with `PATCH`; the writable fields are `name`, `description`, `image_url`, the timeline fields and `owner_character`.
## Legacy and Account Fields
[Section titled “Legacy and Account Fields”](#legacy-and-account-fields)
The standard also defines these. The v2 API does not return them.
| Field | Type | Description |
| --------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_key` | string | Legacy 10-character key, not issued for new worlds. Use world keys (`ow_w_` / `ow_r_`) minted in the [account portal](https://www.onlyworlds.com/account/). |
| `version` | string | The OnlyWorlds format version the world conforms to |
| `user` | string (uuid) | Owner’s account identifier (not exposed on the API) |
# Tools
> The tools that read and write OnlyWorlds worlds, with a link to each.
These tools all work with the same world format, so a world can move between them: as a folder on disk, or through the [API](/docs/development/api-reference). The live directory is at [onlyworlds.com/tools](https://www.onlyworlds.com/tools).
## Build
[Section titled “Build”](#build)
| Tool | What it does | Where |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [Atlas](/docs/tools/atlas) | The main workspace: build, write, map and chart a world that lives as a folder on your disk. | [atlas.onlyworlds.com](https://atlas.onlyworlds.com) |
| [Obsidian Plugin](/docs/tools/obsidian-plugin) | Each element is a markdown note in your Obsidian vault, syncable with online worlds. | [GitHub](https://github.com/OnlyWorlds/obsidian-plugin) |
| [onlyworlds.com](/docs/tools/onlyworlds-com) | Accounts, world hosting, keys and the API. | [onlyworlds.com](https://www.onlyworlds.com) |
| [Easy Mobile](/docs/tools/easy-mobile) | A mobile element editor with local support. | [easy-mobile.onlyworlds.com](https://easy-mobile.onlyworlds.com) |
## Learn and Govern
[Section titled “Learn and Govern”](#learn-and-govern)
| Tool | What it does | Where |
| -------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| [Explorer](/docs/tools/explorer) | An interactive 3D introduction to the schema and the ecosystem. | [explorer.onlyworlds.com](https://explorer.onlyworlds.com) |
| [Council](/docs/tools/council) | Schema governance: motions, precedents, amendments and votes by characters from your worlds. | [council.onlyworlds.com](https://council.onlyworlds.com) |
## Play and Showcase
[Section titled “Play and Showcase”](#play-and-showcase)
| Tool | What it does | Where |
| --------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Tactical Tangle | A hoplite battle game where your OnlyWorlds characters fight in the ranks of a phalanx. | [tangle.onlyworlds.com](https://tangle.onlyworlds.com) |
| DCC Viewer | A parsed book series (Dungeon Crawler Carl) explored as a 3D world graph. | [dcc.onlyworlds.com](https://dcc.onlyworlds.com) |
## Integrations
[Section titled “Integrations”](#integrations)
| Tool | What it does | Where |
| ----------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Foundry Converter | Converts a world folder into an installable Foundry VTT module. | [GitHub](https://github.com/OnlyWorlds/foundry-converter) |
| Azgaar Converter | Turns an Azgaar Fantasy Map Generator export into a world, with a map and pins. | [GitHub](https://github.com/OnlyWorlds/azgaar-converter) |
| Unity SDK | Typed C# models and a client for worlds in the Unity engine. | [Unity SDK](/docs/development/unity) |
For AI tooling see [AI Agents](/docs/development/ai), the [MCP server](/docs/development/mcp) and the [Toolkit](/docs/development/toolkit).
## Retired Tools
[Section titled “Retired Tools”](#retired-tools)
Older tools, most still online but no longer developed, are on the [Legacy](/docs/tools/legacy) page.
# Atlas
> Atlas is the main OnlyWorlds workspace, a local-first web app where a world lives as a folder of plain JSON files.
Atlas reads and writes your world as plain JSON files in a folder on your own machine. The folder is the world, and nothing is locked away.
| | |
| ------------- | ------------------------------------------------------------------------ |
| **URL** | [atlas.onlyworlds.com](https://atlas.onlyworlds.com) |
| **Type** | Local-first web application (desktop Chromium) |
| **Changelog** | [atlas.onlyworlds.com/changelog](https://atlas.onlyworlds.com/changelog) |
## Features
[Section titled “Features”](#features)
* **Build and Browse.** All 22 element categories in a schema-driven editor. Link fields are pickers, and every element lists what links to it.
* **Write.** Books, chapters and scenes (you set the tier names). Names in your prose become links with a click, and the find bar can link every match at once. The words stay yours.
* **Web.** Any element’s connections as a small graph, including everything that links to it.
* **Map.** Your own map images, nested maps, pins for any element, and zones drawn as areas or lines. Zones can be coloured by kind (rivers blue, roads dashed), and the style is saved with the world.
* **Chart.** Place elements on a time axis by their dates, on your world’s own calendar. Charts can be saved and added to published pages.
* **Knowledge.** Track who knows what, per character or circle, and see the world as one of them knows it.
* **Calendars.** Custom calendars as a display layer over world time.
* **Local-first.** Worlds are folders of plain JSON files you own, and Atlas works offline.
* **Sync.** Link a world to onlyworlds.com to push and pull. The folder stays the source of truth.
* **Import and Export.** Import and export the same world file onlyworlds.com exports. A world folder written by another tool opens in place.
* **Sharing.** Publish read-only pages of your world to send to anyone. Needs an account and has a per-account page quota.
* **Light and Dark.** Both themes across the whole app.
## Getting Started
[Section titled “Getting Started”](#getting-started)
No signup is needed to start. A guided tour builds a small demo world with you. Folder mode needs a desktop Chromium browser (Chrome, Edge or Brave). Recent Firefox and Safari can take the tour.
To sync with onlyworlds.com, see [onlyworlds.com](/docs/tools/onlyworlds-com) for accounts and keys.
# Council
> The Council is where the OnlyWorlds schema is governed, through motions, precedents, amendments and votes by characters from users' worlds.
The Council is the community governance tool for the OnlyWorlds schema. Characters delegated from your worlds raise motions, argue precedents, propose amendments and vote on how the schema changes.
| | |
| -------- | -------------------------------------------------------- |
| **URL** | [council.onlyworlds.com](https://council.onlyworlds.com) |
| **Type** | Web application |
## Features
[Section titled “Features”](#features)
* **Motions and Amendments.** Raise a schema limitation as a motion, or propose the change that answers it as an amendment.
* **Precedents.** Answer a motion by showing how the schema already handles it.
* **Delegates.** Commit a character from your own world as your delegate, then vote on what others propose.
* **From an AI Session.** Browse and submit motions through the council skill of the [Toolkit](/docs/development/toolkit) ([GitHub](https://github.com/OnlyWorlds/toolkit)).
# Easy Mobile
> Easy Mobile is a mobile web editor for OnlyWorlds elements, online or offline.
Easy Mobile is a quick mobile web editor for OnlyWorlds elements.
| | |
| -------- | ---------------------------------------------------------------- |
| **URL** | [easy-mobile.onlyworlds.com](https://easy-mobile.onlyworlds.com) |
| **Type** | Mobile web application |
## Features
[Section titled “Features”](#features)
* **Full CRUD.** All 22 element categories, with inline editing.
* **Online and Offline.** Work against the API, or offline in local IndexedDB storage and sync later.
* **Mobile-First.** Touch-friendly, compact layout.
* **Search and Browse.** Global search, infinite scroll and recent elements.
* **Collapsible Sections.** Fields grouped by purpose.
* **Import and Export.** Load from JSON, paste from the clipboard, and export a world.
# Explorer
> Explorer is an interactive 3D introduction to the OnlyWorlds schema and ecosystem.
Explorer is a visual introduction to OnlyWorlds concepts and structure, and a deeper look into the schema.
| | |
| -------- | ---------------------------------------------------------- |
| **URL** | [explorer.onlyworlds.com](https://explorer.onlyworlds.com) |
| **Type** | Learning experience |
## Features
[Section titled “Features”](#features)
* **3D Navigation.** Explore element categories and their relationships in 3D space.
* **Schema Visualization.** See how the OnlyWorlds schema connects.
* **Desktop Optimized.** Best on desktop. Touch devices are sent to a [mobile version](https://explorer-mobile.onlyworlds.com).
# Legacy Tools
> Retired OnlyWorlds tools, their status, and where their work lives now.
These tools are no longer developed. Those marked online still answer at their links, and the rest have been replaced. Current tools are listed on the [Tools](/docs/tools/) page.
## Base Tool
[Section titled “Base Tool”](#base-tool)
An all-round element editor with full field support and both online and offline workflows.
* **Status:** online, no longer developed. [base-tool.onlyworlds.com](https://base-tool.onlyworlds.com)
* Full CRUD on the 22 element categories, with inline editing and relationship management.
* Online (API) or offline (local JSON) modes.
* AI chat through OpenAI, with free daily tokens or a personal key.
* An import pipeline that validates and resolves AI-parsed world data.
## Write Tool
[Section titled “Write Tool”](#write-tool)
A general element editor with writing, presentation and graph features.
* **Status:** online, no longer developed. [onlyworlds.github.io/write-tool](https://onlyworlds.github.io/write-tool/)
* Writing environments for narrative and event editing.
* Graph view of element relationships, and reverse links showing what connects to each element.
* Showcase mode for presentation-ready views, and PDF export of elements.
## Zoner
[Section titled “Zoner”](#zoner)
Draws zones (polygons and lines) on maps using markers.
* **Status:** online, no longer developed. [zoner.onlyworlds.com](https://zoner.onlyworlds.com)
* Click to place markers, then drag them, insert points, or delete with right-click.
* Takes a map image URL, or works on a grid fallback.
* Optional sync to OnlyWorlds as Map, Zone and Marker elements, or a fully local workflow.
* Exports JSON compatible with the Base Tool migrator.
## Mobile Companion
[Section titled “Mobile Companion”](#mobile-companion)
The oldest tool in the directory: element creation and editing with a progression system that teaches OnlyWorlds concepts while you build.
* **Status:** Android beta APK only, no longer developed. The iOS beta is closed. [Android APK](https://drive.google.com/file/d/1ZBgudPtApUy6eR-kE0OuMKkGBF61aru0/view?usp=sharing)
* Offline creation with manual sync.
* A timeline prototype for mapping events and customizing time settings.
* Guided challenges that unlock rewards and features.
To install the APK, enable installation from unknown sources in your Android settings, download the file from Google Drive, install it, then fetch an existing world or start a new one.
## Little Lens (Element Viewer)
[Section titled “Little Lens (Element Viewer)”](#little-lens-element-viewer)
A read-only viewer for browsing, searching and exploring a world’s elements and their connections.
* **Status:** online, no longer developed. [little-lens.onlyworlds.com](https://little-lens.onlyworlds.com/)
* Card grid with type-coloured icons, descriptions and link counts.
* Filters across 18 browsable categories, search over names and descriptions, and sorting alphabetically or by connections.
* Detail view with all fields, outgoing and reverse connections, and element images.
* A radial connection graph, a timeline bar, and a breadcrumb trail with browser back and forward support.
* Moppetopia pre-loaded as a demo world.
## Tool History
[Section titled “Tool History”](#tool-history)
Tools that have been retired and replaced:
* **Text Tool.** An AI-parsing and YAML-editing tool, retired with the 2026 platform rebuild. Parsing now lives in the [Toolkit](/docs/development/toolkit) and the [MCP server](/docs/development/mcp).
* **Map Tool.** Maps, pins and hierarchies, retired with the same rebuild. Mapping now lives in [Atlas](/docs/tools/atlas).
* **The Old Browser Editor.** The first web workspace, superseded by [Atlas](/docs/tools/atlas) and the [onlyworlds.com](/docs/tools/onlyworlds-com) portal.
# Obsidian Plugin
> The OnlyWorlds Builder plugin keeps each element as a markdown note in an Obsidian vault, with optional sync to onlyworlds.com.
The OnlyWorlds Builder is an Obsidian community plugin. Each element is a plain markdown note in your vault, with clickable links between elements.
| | |
| ------------- | -------------------------------------------------------------------------------------- |
| **URL** | [github.com/OnlyWorlds/obsidian-plugin](https://github.com/OnlyWorlds/obsidian-plugin) |
| **Type** | Obsidian community plugin |
| **Platforms** | Obsidian on desktop and mobile |
## Features
[Section titled “Features”](#features)
* **Vault Integration.** Create and edit elements as Obsidian notes.
* **Local-Only Worlds.** Build a world entirely in your vault, no account needed.
* **Optional Sync.** Connect a world to onlyworlds.com, or take a local-only world online later.
* **World Folder Import and Export.** Read and write OnlyWorlds world folders, which open directly in [Atlas](/docs/tools/atlas).
* **Commands.** World operations are available in the Obsidian command palette.
## Setup
[Section titled “Setup”](#setup)
1. Install Obsidian (desktop or mobile).
2. Enable community plugins in Obsidian settings.
3. Search for “onlyworlds” in the plugin browser.
4. Install the OnlyWorlds Builder plugin.
5. Create or import worlds with the OnlyWorlds commands.
6. Export worlds or individual elements from the resulting notes.
# onlyworlds.com
> The OnlyWorlds platform: accounts, world hosting, keys and the API.
onlyworlds.com is the platform hub: accounts, world hosting, keys and the API. Editing happens in [Atlas](/docs/tools/atlas) and the other tools, and onlyworlds.com is where your worlds live online.
| | |
| -------- | ------------------------------------------------------------- |
| **URL** | [onlyworlds.com/account](https://www.onlyworlds.com/account/) |
| **Type** | Web application |
## Features
[Section titled “Features”](#features)
* **World Hosting.** Create worlds and manage them from the account portal.
* **Keys.** Mint and revoke scoped world keys, `ow_w_` for write and `ow_r_` for read. A key is shown once. See [Keys](/docs/getting-started/keys).
* **Account Tokens.** Mint `ow_a_` tokens in Settings so apps can act on your account.
* **Element Viewer.** Browse the elements of your worlds read-only, from the portal.
* **Export.** Download a world as a single JSON file.
* **API.** Full REST access, documented at [api/docs](https://www.onlyworlds.com/api/docs), and an [MCP server](https://www.onlyworlds.com/mcp) for AI assistants. See the [API reference](/docs/development/api-reference) and [MCP](/docs/development/mcp).
## Getting Started
[Section titled “Getting Started”](#getting-started)
1. Create an account at [onlyworlds.com](https://www.onlyworlds.com/accounts/login/), with email or Google, GitHub or Discord.
2. Create a world. Your first world sets your 4-digit PIN, which is the write wall on all your worlds.
3. Mint a world key from the world page, for tool and API access.
4. Open the world in [Atlas](https://atlas.onlyworlds.com) or any other tool.
# Contact and Resources
> Where to ask questions, report issues, learn the schema, and support OnlyWorlds.
OnlyWorlds is free to use and extend.
## Community
[Section titled “Community”](#community)
* **[Discord](https://discord.gg/twCjqvVBwb).** Chat, feedback and tool requests.
* **[Feedback](https://www.onlyworlds.com/feedback).** Report issues and request features.
* **[GitHub Discussions](https://github.com/OnlyWorlds/OnlyWorlds/discussions).** Announcements and technical discussion.
* **[Council](/docs/tools/council).** Delegate your characters to shape the OnlyWorlds standard ([council.onlyworlds.com](https://council.onlyworlds.com)).
## Learn
[Section titled “Learn”](#learn)
* **[YouTube](https://youtu.be/1IEazx8wg4I).** The welcome video.
* **[Podcast](https://onlyworlds.com/podcast).** A NotebookLM conversation about the project and its history.
* **[Explorer](/docs/tools/explorer).** A visual tutorial about the schema and infrastructure ([explorer.onlyworlds.com](https://explorer.onlyworlds.com)).
## Support
[Section titled “Support”](#support)
* **[Patreon](https://www.patreon.com/cw/OnlyWorlds).** Support the project.
* **[Email](mailto:info@onlyworlds.com).** General inquiries.
# Changelog
> Notable changes to the OnlyWorlds docs, newest first.
Notable changes to these docs, newest first. Changes to the packages are in each package’s own changelog: [TypeScript SDK](https://github.com/OnlyWorlds/sdk/blob/main/CHANGELOG.md), [Unity SDK](https://github.com/OnlyWorlds/unity-sdk/blob/main/Packages/com.onlyworlds.sdk/CHANGELOG.md).
## October 2026: the docs rebuilt
[Section titled “October 2026: the docs rebuilt”](#october-2026-the-docs-rebuilt)
The docs moved from Jekyll to [Starlight](https://starlight.astro.build), at the same address.
* **Every old URL still answers.** Pages kept their paths. The error links in API responses (`/api/errors#`) and the LLM guide (`/assets/ow_llm_guide.txt`) are unchanged. Retired tool pages now forward to [Legacy Tools](/docs/tools/legacy).
* **The API guide is split into topics:** [reading](/docs/development/api/reads), [link fields](/docs/development/api/links), [writes and bulk](/docs/development/api/writes), [changes](/docs/development/api/changes), [members](/docs/development/api/members), [images](/docs/development/api/images), [CORS](/docs/development/api/cors) and [migrating from v1](/docs/development/api/classic).
* **New pages:** [Keys and PINs](/docs/getting-started/keys), [Conventions](/docs/schema/conventions), the [TypeScript](/docs/development/typescript), [Python](/docs/development/python) and [Unity](/docs/development/unity) clients, [Games](/docs/development/games), [Agent Seats](/docs/development/agents) and the [Toolkit](/docs/development/toolkit).
* **The field tables on the element pages are generated** from the published schema ([schema-dist](https://github.com/OnlyWorlds/schema-dist)) on every build. They show each field’s name as the API sends it.
* **For AI agents:** every page has a Markdown copy at the same path with `.md` (for example `/docs/development/typescript.md`), and the site has [`llms.txt`](/llms.txt), [`llms-small.txt`](/llms-small.txt) and [`llms-full.txt`](/llms-full.txt). Each page has Copy Markdown and Open in Claude or ChatGPT.
* **Search** covers every page (Ctrl K).
The previous site is kept whole in the repository, on the `jekyll` branch.