{"openapi": "3.1.0", "info": {"title": "OnlyWorlds API", "version": "2.0", "description": "\nThe OnlyWorlds platform API: worlds of typed, linked elements (22 types), served\nfor any tool to read and write. Base URL: `https://www.onlyworlds.com/api/v2`.\nTo try it, read the public demo world (Moppetopia) with `API-Key: 0000000001`.\n\n**Authentication** is header-based, per world. `API-Key` names the world\n(a scoped `ow_r_`/`ow_w_` key from your [account page](/account/), or a classic\n10-digit key). Writes also need `API-Pin`: your 4-digit account PIN, or an\nagent seat's `ow_s_` secret. The key says which world, the PIN proves it's you.\nPrefixed keys (`ow_r_`, `ow_w_`) read without it, and `ow_r_` read keys are made\nfor sharing; a classic key on a private world sends its PIN for reads too.\n\n**Keys and roles.** A world's *owner* is the account that made it, and mints\nits `ow_r_` (read) and `ow_w_` (write) keys. A *classic key* is the 10-digit key\nevery world had before prefixed keys; it acts as the owner. An `ow_a_` account\ntoken is for the `/account` routes only. The owner can invite *members*, each\nwith their own key and a role: `co_builder` (changes anything), `contributor`\n(changes only what it created) or `guest` (sees only what it made, what its\nCharacter is on or knows, and who is here, and knows of what its Character's\nown sheet links to, one step out, unless hidden: a stub of its name, description,\nkind and picture (a Character's also of its physicality, height, weight, species\nand reputation), marked `known_of`; changes only what it made, and its\nnotes, Narratives, are told by its own Character). An *agent seat* is\na member that is a tool or an AI agent rather than a person: it redeems a join\ncode at `POST /join` for a key and an `ow_s_` secret (its `API-Pin`), and its\n*sponsor* is the person who answers for it.\n\n**Who changed it.** Every element carries `changed_by` (the membership that\nlast changed it, null for the owner) and `changed_via`, the door it came\nthrough: `seat` (an agent seat), `mcp`, `declared`, `api`, `v1`, `sync` or\n`server`. A tool whose agent writes with a person's key sends the header\n`X-OW-Agent` (any value; it is never stored), and its writes read `declared`.\n`seat`, `mcp` and `declared` mean an agent made it.\n\n**Limits.** Each key or account token may make 300 requests a minute, reads and\nwrites together (a `/bulk` call is one request), and a request with neither 60 a\nminute per address; every reply but the health checks says where you stand in\n`RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds to the\nminute's end), and the request over it answers 429 `too_many_requests` with\n`Retry-After`. Image uploads have a daily limit per world. Failed PINs are\nthrottled (429 `rate_limited`), and when every request slot is busy any route\nanswers 503 `server_busy` with `Retry-After`. To stay in sync, follow `GET /changes` with\nyour cursor rather than re-reading the lists, a few polls a minute at most;\n`?head=true` asks \"anything new?\" without a page. A first full sync may page\nthrough as fast as the answers come.\n\n**The classic API** (used by most existing tools, guides, and SDK versions)\nlives at `/api/worldapi/` and keeps working indefinitely, with the same data in\nthe original wire format. It is documented in the\n[developer reference](https://onlyworlds.github.io/docs/development/api-reference)\nrather than here. New integrations should prefer this API: one-shape links,\nbulk writes with cycle resolution, a `/changes` sync feed, and honest errors.\n\n**From a browser**: cross-origin calls are allowed for OnlyWorlds' own sites\n(`*.onlyworlds.com`), the common free hosts (GitHub Pages `*.github.io`,\nNetlify, Vercel, Cloudflare Pages `*.pages.dev`, Firebase, GitLab Pages\nand others) and `http://localhost` on any port during development. A web app\non any other domain calls the API from its own server.\n\nAgents: the agent policy at https://www.onlyworlds.com/agents says what an agent\nmay do and what the platform records.\n\n**Versions and deprecation.** The path carries the API's version: `/api/v2` is\ncurrent. The old `/api/worldapi` (v1) keeps answering for the tools built on it;\nit is frozen, not developed. New fields and routes arrive without notice and\nnever change what existing fields mean. A field, route or behaviour is removed\nonly after it has been announced on the feedback page's changelog and in this\ndocument, at least six months ahead. The `X-OW-Schema-Version` header says which\nversion of the standard shapes a reply.\n"}, "paths": {"/api/v2/health": {"get": {"operationId": "get_health", "summary": "Health", "parameters": [], "responses": {"200": {"description": "OK"}}, "description": "Liveness: 200 when the process is up. No credentials needed.", "tags": ["Health"]}}, "/api/v2/health/deep": {"get": {"operationId": "get_health_deep", "summary": "Health Deep", "parameters": [], "responses": {"200": {"description": "Every component is ok."}, "503": {"description": "At least one component is failing (named in the body)."}}, "description": "Dependency check: 200 when the database and cache pass, else 503\nnaming the failing ones.", "tags": ["Health"]}}, "/api/v2/ability": {"get": {"operationId": "list_ability", "summary": "List ability elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["tradition", "source", "locus", "effects", "talents", "requisites", "instruments", "systems"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, activation, challenges, prevalence, duration, potency, range, tradition, source, locus, effects, talents, requisites, instruments, systems, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AbilityList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_ability", "summary": "Create an ability", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ability"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AbilityWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/ability/{id}": {"get": {"operationId": "get_ability", "summary": "Get one ability", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["tradition", "source", "locus", "effects", "talents", "requisites", "instruments", "systems"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, activation, challenges, prevalence, duration, potency, range, tradition, source, locus, effects, talents, requisites, instruments, systems, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ability"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_ability", "summary": "Create or replace an ability by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ability"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ability"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AbilityWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_ability", "summary": "Update part of an ability", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ability"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Ability"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AbilityPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_ability", "summary": "Delete an ability", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/ability/{id}/links/{field}": {"post": {"operationId": "link_ability", "summary": "Add or remove ids on one of an ability's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["effects", "talents", "requisites", "instruments", "systems"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ability"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Ability"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Ability"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/ability/{id}/files": {"get": {"operationId": "list_ability_files", "summary": "List a ability's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Ability"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_ability_file_ticket", "summary": "Get a ticket to attach a file to a ability", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Ability"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/ability/{id}/files/{file_id}/download": {"get": {"operationId": "get_ability_file_download", "summary": "Get a download link for a ability's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Ability"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/ability/{id}/files/{file_id}": {"delete": {"operationId": "delete_ability_file", "summary": "Remove a ability's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Ability"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/ability/{id}/hidden": {"put": {"operationId": "hide_ability", "summary": "Hide a ability", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Ability"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_ability", "summary": "Unhide a ability", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The ability's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Ability"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective": {"get": {"operationId": "list_collective", "summary": "List collective elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["operator", "equipment", "abilities", "symbolism", "species", "characters", "creatures", "phenomena"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, composition, activity, disposition, state, count, formation_date, operator, equipment, abilities, symbolism, species, characters, creatures, phenomena, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "characters", "required": false, "schema": {"type": "string", "format": "uuid"}, "description": "A Character id: elements whose `characters` link contains it. A malformed id is a 422 (param characters)."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CollectiveList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_collective", "summary": "Create a collective", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Collective"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CollectiveWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective/{id}": {"get": {"operationId": "get_collective", "summary": "Get one collective", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["operator", "equipment", "abilities", "symbolism", "species", "characters", "creatures", "phenomena"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, composition, activity, disposition, state, count, formation_date, operator, equipment, abilities, symbolism, species, characters, creatures, phenomena, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Collective"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_collective", "summary": "Create or replace a collective by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Collective"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Collective"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CollectiveWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_collective", "summary": "Update part of a collective", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Collective"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Collective"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CollectivePatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_collective", "summary": "Delete a collective", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective/{id}/links/{field}": {"post": {"operationId": "link_collective", "summary": "Add or remove ids on one of a collective's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["equipment", "abilities", "symbolism", "species", "characters", "creatures", "phenomena"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Collective"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Collective"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Collective"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective/{id}/files": {"get": {"operationId": "list_collective_files", "summary": "List a collective's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Collective"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_collective_file_ticket", "summary": "Get a ticket to attach a file to a collective", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Collective"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective/{id}/files/{file_id}/download": {"get": {"operationId": "get_collective_file_download", "summary": "Get a download link for a collective's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Collective"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective/{id}/files/{file_id}": {"delete": {"operationId": "delete_collective_file", "summary": "Remove a collective's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Collective"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/collective/{id}/hidden": {"put": {"operationId": "hide_collective", "summary": "Hide a collective", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Collective"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_collective", "summary": "Unhide a collective", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The collective's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Collective"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character": {"get": {"operationId": "list_character", "summary": "List character elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["birthplace", "location", "species", "traits", "abilities", "languages", "objects", "institutions", "family", "friends", "rivals"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, physicality, mentality, background, motivations, reputation, height, weight, birth_date, charisma, coercion, competence, compassion, creativity, courage, level, hit_points, STR, DEX, CON, INT, WIS, CHA, birthplace, location, species, traits, abilities, languages, objects, institutions, family, friends, rivals, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CharacterList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_character", "summary": "Create a character", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Character"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CharacterWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character/{id}": {"get": {"operationId": "get_character", "summary": "Get one character", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["birthplace", "location", "species", "traits", "abilities", "languages", "objects", "institutions", "family", "friends", "rivals"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, physicality, mentality, background, motivations, reputation, height, weight, birth_date, charisma, coercion, competence, compassion, creativity, courage, level, hit_points, STR, DEX, CON, INT, WIS, CHA, birthplace, location, species, traits, abilities, languages, objects, institutions, family, friends, rivals, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Character"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_character", "summary": "Create or replace a character by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Character"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Character"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CharacterWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_character", "summary": "Update part of a character", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Character"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Character"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CharacterPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_character", "summary": "Delete a character", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character/{id}/links/{field}": {"post": {"operationId": "link_character", "summary": "Add or remove ids on one of a character's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["species", "traits", "abilities", "languages", "objects", "institutions", "family", "friends", "rivals"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Character"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Character"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Character"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character/{id}/files": {"get": {"operationId": "list_character_files", "summary": "List a character's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Character"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_character_file_ticket", "summary": "Get a ticket to attach a file to a character", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Character"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character/{id}/files/{file_id}/download": {"get": {"operationId": "get_character_file_download", "summary": "Get a download link for a character's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Character"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character/{id}/files/{file_id}": {"delete": {"operationId": "delete_character_file", "summary": "Remove a character's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Character"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/character/{id}/hidden": {"put": {"operationId": "hide_character", "summary": "Hide a character", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Character"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_character", "summary": "Unhide a character", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The character's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Character"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct": {"get": {"operationId": "list_construct", "summary": "List construct elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["founder", "custodian", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, rationale, history, status, reach, start_date, end_date, founder, custodian, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, relations, titles, constructs, events, narratives, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "characters", "required": false, "schema": {"type": "string", "format": "uuid"}, "description": "A Character id: elements whose `characters` link contains it. A malformed id is a 422 (param characters)."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConstructList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_construct", "summary": "Create a construct", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Construct"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConstructWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct/{id}": {"get": {"operationId": "get_construct", "summary": "Get one construct", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["founder", "custodian", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, rationale, history, status, reach, start_date, end_date, founder, custodian, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, relations, titles, constructs, events, narratives, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Construct"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_construct", "summary": "Create or replace a construct by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Construct"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Construct"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConstructWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_construct", "summary": "Update part of a construct", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Construct"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Construct"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConstructPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_construct", "summary": "Delete a construct", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct/{id}/links/{field}": {"post": {"operationId": "link_construct", "summary": "Add or remove ids on one of a construct's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Construct"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Construct"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Construct"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct/{id}/files": {"get": {"operationId": "list_construct_files", "summary": "List a construct's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Construct"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_construct_file_ticket", "summary": "Get a ticket to attach a file to a construct", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Construct"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct/{id}/files/{file_id}/download": {"get": {"operationId": "get_construct_file_download", "summary": "Get a download link for a construct's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Construct"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct/{id}/files/{file_id}": {"delete": {"operationId": "delete_construct_file", "summary": "Remove a construct's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Construct"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/construct/{id}/hidden": {"put": {"operationId": "hide_construct", "summary": "Hide a construct", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Construct"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_construct", "summary": "Unhide a construct", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The construct's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Construct"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature": {"get": {"operationId": "list_creature", "summary": "List creature elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["location", "zone", "species", "traits", "abilities", "languages", "actions"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, appearance, habits, demeanor, status, weight, height, birth_date, challenge_rating, hit_points, armor_class, speed, location, zone, species, traits, abilities, languages, actions, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreatureList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_creature", "summary": "Create a creature", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Creature"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreatureWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature/{id}": {"get": {"operationId": "get_creature", "summary": "Get one creature", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["location", "zone", "species", "traits", "abilities", "languages", "actions"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, appearance, habits, demeanor, status, weight, height, birth_date, challenge_rating, hit_points, armor_class, speed, location, zone, species, traits, abilities, languages, actions, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Creature"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_creature", "summary": "Create or replace a creature by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Creature"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Creature"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreatureWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_creature", "summary": "Update part of a creature", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Creature"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Creature"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreaturePatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_creature", "summary": "Delete a creature", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature/{id}/links/{field}": {"post": {"operationId": "link_creature", "summary": "Add or remove ids on one of a creature's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["species", "traits", "abilities", "languages", "actions"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Creature"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Creature"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Creature"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature/{id}/files": {"get": {"operationId": "list_creature_files", "summary": "List a creature's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Creature"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_creature_file_ticket", "summary": "Get a ticket to attach a file to a creature", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Creature"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature/{id}/files/{file_id}/download": {"get": {"operationId": "get_creature_file_download", "summary": "Get a download link for a creature's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Creature"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature/{id}/files/{file_id}": {"delete": {"operationId": "delete_creature_file", "summary": "Remove a creature's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Creature"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/creature/{id}/hidden": {"put": {"operationId": "hide_creature", "summary": "Hide a creature", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Creature"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_creature", "summary": "Unhide a creature", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The creature's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Creature"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event": {"get": {"operationId": "list_event", "summary": "List event elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["triggers", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, history, challenges, consequences, start_date, end_date, triggers, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, relations, titles, constructs, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "characters", "required": false, "schema": {"type": "string", "format": "uuid"}, "description": "A Character id: elements whose `characters` link contains it. A malformed id is a 422 (param characters)."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_event", "summary": "Create an event", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Event"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event/{id}": {"get": {"operationId": "get_event", "summary": "Get one event", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["triggers", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, history, challenges, consequences, start_date, end_date, triggers, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, relations, titles, constructs, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Event"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_event", "summary": "Create or replace an event by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Event"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Event"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_event", "summary": "Update part of an event", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Event"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Event"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_event", "summary": "Delete an event", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event/{id}/links/{field}": {"post": {"operationId": "link_event", "summary": "Add or remove ids on one of an event's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["triggers", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Event"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Event"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Event"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event/{id}/files": {"get": {"operationId": "list_event_files", "summary": "List a event's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Event"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_event_file_ticket", "summary": "Get a ticket to attach a file to a event", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Event"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event/{id}/files/{file_id}/download": {"get": {"operationId": "get_event_file_download", "summary": "Get a download link for a event's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Event"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event/{id}/files/{file_id}": {"delete": {"operationId": "delete_event_file", "summary": "Remove a event's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Event"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/event/{id}/hidden": {"put": {"operationId": "hide_event", "summary": "Hide a event", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Event"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_event", "summary": "Unhide a event", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The event's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Event"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family": {"get": {"operationId": "list_family", "summary": "List family elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["traditions", "traits", "abilities", "languages", "ancestors", "estates", "governs", "heirlooms", "creatures"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, spirit, history, reputation, traditions, traits, abilities, languages, ancestors, estates, governs, heirlooms, creatures, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FamilyList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_family", "summary": "Create a family", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Family"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FamilyWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family/{id}": {"get": {"operationId": "get_family", "summary": "Get one family", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["traditions", "traits", "abilities", "languages", "ancestors", "estates", "governs", "heirlooms", "creatures"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, spirit, history, reputation, traditions, traits, abilities, languages, ancestors, estates, governs, heirlooms, creatures, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Family"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_family", "summary": "Create or replace a family by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Family"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Family"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FamilyWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_family", "summary": "Update part of a family", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Family"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Family"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FamilyPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_family", "summary": "Delete a family", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family/{id}/links/{field}": {"post": {"operationId": "link_family", "summary": "Add or remove ids on one of a family's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["traditions", "traits", "abilities", "languages", "ancestors", "estates", "governs", "heirlooms", "creatures"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Family"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Family"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Family"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family/{id}/files": {"get": {"operationId": "list_family_files", "summary": "List a family's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Family"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_family_file_ticket", "summary": "Get a ticket to attach a file to a family", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Family"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family/{id}/files/{file_id}/download": {"get": {"operationId": "get_family_file_download", "summary": "Get a download link for a family's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Family"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family/{id}/files/{file_id}": {"delete": {"operationId": "delete_family_file", "summary": "Remove a family's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Family"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/family/{id}/hidden": {"put": {"operationId": "hide_family", "summary": "Hide a family", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Family"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_family", "summary": "Unhide a family", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The family's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Family"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution": {"get": {"operationId": "list_institution", "summary": "List institution elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_institution", "zones", "objects", "creatures", "allies", "adversaries", "constructs"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, doctrine, status, founding_date, parent_institution, zones, objects, creatures, allies, adversaries, constructs, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InstitutionList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_institution", "summary": "Create an institution", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Institution"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InstitutionWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution/{id}": {"get": {"operationId": "get_institution", "summary": "Get one institution", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_institution", "zones", "objects", "creatures", "allies", "adversaries", "constructs"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, doctrine, status, founding_date, parent_institution, zones, objects, creatures, allies, adversaries, constructs, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Institution"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_institution", "summary": "Create or replace an institution by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Institution"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Institution"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InstitutionWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_institution", "summary": "Update part of an institution", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Institution"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Institution"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InstitutionPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_institution", "summary": "Delete an institution", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution/{id}/links/{field}": {"post": {"operationId": "link_institution", "summary": "Add or remove ids on one of an institution's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["zones", "objects", "creatures", "allies", "adversaries", "constructs"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Institution"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Institution"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Institution"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution/{id}/files": {"get": {"operationId": "list_institution_files", "summary": "List a institution's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Institution"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_institution_file_ticket", "summary": "Get a ticket to attach a file to a institution", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Institution"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution/{id}/files/{file_id}/download": {"get": {"operationId": "get_institution_file_download", "summary": "Get a download link for a institution's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Institution"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution/{id}/files/{file_id}": {"delete": {"operationId": "delete_institution_file", "summary": "Remove a institution's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Institution"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/institution/{id}/hidden": {"put": {"operationId": "hide_institution", "summary": "Hide a institution", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Institution"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_institution", "summary": "Unhide a institution", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The institution's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Institution"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language": {"get": {"operationId": "list_language", "summary": "List language elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["classification", "spread", "dialects"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, phonology, grammar, lexicon, writing, status, classification, spread, dialects, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguageList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_language", "summary": "Create a language", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguageWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language/{id}": {"get": {"operationId": "get_language", "summary": "Get one language", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["classification", "spread", "dialects"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, phonology, grammar, lexicon, writing, status, classification, spread, dialects, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_language", "summary": "Create or replace a language by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguageWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_language", "summary": "Update part of a language", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Language"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LanguagePatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_language", "summary": "Delete a language", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language/{id}/links/{field}": {"post": {"operationId": "link_language", "summary": "Add or remove ids on one of a language's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["spread", "dialects"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Language"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Language"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Language"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language/{id}/files": {"get": {"operationId": "list_language_files", "summary": "List a language's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Language"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_language_file_ticket", "summary": "Get a ticket to attach a file to a language", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Language"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language/{id}/files/{file_id}/download": {"get": {"operationId": "get_language_file_download", "summary": "Get a download link for a language's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Language"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language/{id}/files/{file_id}": {"delete": {"operationId": "delete_language_file", "summary": "Remove a language's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Language"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/language/{id}/hidden": {"put": {"operationId": "hide_language", "summary": "Hide a language", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Language"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_language", "summary": "Unhide a language", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The language's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Language"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law": {"get": {"operationId": "list_law", "summary": "List law elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_law", "author", "penalties", "locations", "zones", "prohibitions", "adjudicators", "enforcers"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, declaration, purpose, date, parent_law, author, penalties, locations, zones, prohibitions, adjudicators, enforcers, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LawList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_law", "summary": "Create a law", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Law"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LawWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law/{id}": {"get": {"operationId": "get_law", "summary": "Get one law", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_law", "author", "penalties", "locations", "zones", "prohibitions", "adjudicators", "enforcers"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, declaration, purpose, date, parent_law, author, penalties, locations, zones, prohibitions, adjudicators, enforcers, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Law"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_law", "summary": "Create or replace a law by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Law"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Law"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LawWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_law", "summary": "Update part of a law", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Law"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Law"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LawPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_law", "summary": "Delete a law", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law/{id}/links/{field}": {"post": {"operationId": "link_law", "summary": "Add or remove ids on one of a law's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["penalties", "locations", "zones", "prohibitions", "adjudicators", "enforcers"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Law"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Law"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Law"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law/{id}/files": {"get": {"operationId": "list_law_files", "summary": "List a law's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Law"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_law_file_ticket", "summary": "Get a ticket to attach a file to a law", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Law"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law/{id}/files/{file_id}/download": {"get": {"operationId": "get_law_file_download", "summary": "Get a download link for a law's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Law"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law/{id}/files/{file_id}": {"delete": {"operationId": "delete_law_file", "summary": "Remove a law's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Law"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/law/{id}/hidden": {"put": {"operationId": "hide_law", "summary": "Hide a law", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Law"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_law", "summary": "Unhide a law", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The law's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Law"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location": {"get": {"operationId": "list_location", "summary": "List location elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_location", "primary_power", "governing_title", "zone", "rival", "partner", "populations", "secondary_powers", "founders", "cults", "delicacies", "extraction_methods", "extraction_goods", "industry_methods", "industry_goods", "extraction_markets", "industry_markets", "currencies", "buildings", "building_methods", "fighters", "defensive_objects"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, form, function, political_climate, customs, infrastructure, architecture, defensibility, founding_date, elevation, parent_location, primary_power, governing_title, zone, rival, partner, populations, secondary_powers, founders, cults, delicacies, extraction_methods, extraction_goods, industry_methods, industry_goods, extraction_markets, industry_markets, currencies, buildings, building_methods, fighters, defensive_objects, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocationList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_location", "summary": "Create a location", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Location"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocationWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location/{id}": {"get": {"operationId": "get_location", "summary": "Get one location", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_location", "primary_power", "governing_title", "zone", "rival", "partner", "populations", "secondary_powers", "founders", "cults", "delicacies", "extraction_methods", "extraction_goods", "industry_methods", "industry_goods", "extraction_markets", "industry_markets", "currencies", "buildings", "building_methods", "fighters", "defensive_objects"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, form, function, political_climate, customs, infrastructure, architecture, defensibility, founding_date, elevation, parent_location, primary_power, governing_title, zone, rival, partner, populations, secondary_powers, founders, cults, delicacies, extraction_methods, extraction_goods, industry_methods, industry_goods, extraction_markets, industry_markets, currencies, buildings, building_methods, fighters, defensive_objects, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Location"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_location", "summary": "Create or replace a location by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Location"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Location"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocationWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_location", "summary": "Update part of a location", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Location"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Location"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocationPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_location", "summary": "Delete a location", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location/{id}/links/{field}": {"post": {"operationId": "link_location", "summary": "Add or remove ids on one of a location's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["populations", "secondary_powers", "founders", "cults", "delicacies", "extraction_methods", "extraction_goods", "industry_methods", "industry_goods", "extraction_markets", "industry_markets", "currencies", "buildings", "building_methods", "fighters", "defensive_objects"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Location"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Location"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Location"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location/{id}/files": {"get": {"operationId": "list_location_files", "summary": "List a location's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Location"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_location_file_ticket", "summary": "Get a ticket to attach a file to a location", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Location"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location/{id}/files/{file_id}/download": {"get": {"operationId": "get_location_file_download", "summary": "Get a download link for a location's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Location"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location/{id}/files/{file_id}": {"delete": {"operationId": "delete_location_file", "summary": "Remove a location's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Location"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/location/{id}/hidden": {"put": {"operationId": "hide_location", "summary": "Hide a location", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Location"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_location", "summary": "Unhide a location", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The location's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Location"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map": {"get": {"operationId": "list_map", "summary": "List map elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_map", "location"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, background_color, hierarchy, width, height, depth, parent_map, location, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MapList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_map", "summary": "Create a map", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Map"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MapWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map/{id}": {"get": {"operationId": "get_map", "summary": "Get one map", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_map", "location"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, background_color, hierarchy, width, height, depth, parent_map, location, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Map"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_map", "summary": "Create or replace a map by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Map"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Map"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MapWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_map", "summary": "Update part of a map", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Map"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Map"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MapPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_map", "summary": "Delete a map", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map/{id}/links/{field}": {"post": {"operationId": "link_map", "summary": "Add or remove ids on one of a map's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string"}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Map"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Map"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Map"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map/{id}/files": {"get": {"operationId": "list_map_files", "summary": "List a map's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Map"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_map_file_ticket", "summary": "Get a ticket to attach a file to a map", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Map"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map/{id}/files/{file_id}/download": {"get": {"operationId": "get_map_file_download", "summary": "Get a download link for a map's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Map"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map/{id}/files/{file_id}": {"delete": {"operationId": "delete_map_file", "summary": "Remove a map's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Map"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/map/{id}/hidden": {"put": {"operationId": "hide_map", "summary": "Hide a map", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Map"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_map", "summary": "Unhide a map", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The map's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Map"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker": {"get": {"operationId": "list_marker", "summary": "List marker elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["map", "zone"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, x, y, z, order, map, zone, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MarkerList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_marker", "summary": "Create a marker", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Marker"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MarkerWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker/{id}": {"get": {"operationId": "get_marker", "summary": "Get one marker", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["map", "zone"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, x, y, z, order, map, zone, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Marker"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_marker", "summary": "Create or replace a marker by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Marker"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Marker"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MarkerWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_marker", "summary": "Update part of a marker", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Marker"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Marker"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MarkerPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_marker", "summary": "Delete a marker", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker/{id}/links/{field}": {"post": {"operationId": "link_marker", "summary": "Add or remove ids on one of a marker's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string"}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Marker"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Marker"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Marker"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker/{id}/files": {"get": {"operationId": "list_marker_files", "summary": "List a marker's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Marker"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_marker_file_ticket", "summary": "Get a ticket to attach a file to a marker", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Marker"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker/{id}/files/{file_id}/download": {"get": {"operationId": "get_marker_file_download", "summary": "Get a download link for a marker's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Marker"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker/{id}/files/{file_id}": {"delete": {"operationId": "delete_marker_file", "summary": "Remove a marker's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Marker"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/marker/{id}/hidden": {"put": {"operationId": "hide_marker", "summary": "Hide a marker", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Marker"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_marker", "summary": "Unhide a marker", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The marker's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Marker"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative": {"get": {"operationId": "list_narrative", "summary": "List narrative elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_narrative", "protagonist", "antagonist", "narrator", "conservator", "events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, story, consequences, start_date, end_date, order, parent_narrative, protagonist, antagonist, narrator, conservator, events, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, relations, titles, constructs, laws, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "characters", "required": false, "schema": {"type": "string", "format": "uuid"}, "description": "A Character id: elements whose `characters` link contains it. A malformed id is a 422 (param characters)."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NarrativeList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_narrative", "summary": "Create a narrative", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Narrative"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NarrativeWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative/{id}": {"get": {"operationId": "get_narrative", "summary": "Get one narrative", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_narrative", "protagonist", "antagonist", "narrator", "conservator", "events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, story, consequences, start_date, end_date, order, parent_narrative, protagonist, antagonist, narrator, conservator, events, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, relations, titles, constructs, laws, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Narrative"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_narrative", "summary": "Create or replace a narrative by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Narrative"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Narrative"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NarrativeWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_narrative", "summary": "Update part of a narrative", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Narrative"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Narrative"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NarrativePatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_narrative", "summary": "Delete a narrative", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative/{id}/links/{field}": {"post": {"operationId": "link_narrative", "summary": "Add or remove ids on one of a narrative's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Narrative"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Narrative"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Narrative"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative/{id}/files": {"get": {"operationId": "list_narrative_files", "summary": "List a narrative's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Narrative"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_narrative_file_ticket", "summary": "Get a ticket to attach a file to a narrative", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Narrative"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative/{id}/files/{file_id}/download": {"get": {"operationId": "get_narrative_file_download", "summary": "Get a download link for a narrative's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Narrative"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative/{id}/files/{file_id}": {"delete": {"operationId": "delete_narrative_file", "summary": "Remove a narrative's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Narrative"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/narrative/{id}/hidden": {"put": {"operationId": "hide_narrative", "summary": "Hide a narrative", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Narrative"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_narrative", "summary": "Unhide a narrative", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The narrative's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Narrative"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object": {"get": {"operationId": "list_object", "summary": "List object elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_object", "location", "language", "materials", "technology", "effects", "abilities", "consumes", "affinities"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, aesthetics, utility, origins, weight, amount, parent_object, location, language, materials, technology, effects, abilities, consumes, affinities, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ObjectList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_object", "summary": "Create an object", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Object"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ObjectWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object/{id}": {"get": {"operationId": "get_object", "summary": "Get one object", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_object", "location", "language", "materials", "technology", "effects", "abilities", "consumes", "affinities"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, aesthetics, utility, origins, weight, amount, parent_object, location, language, materials, technology, effects, abilities, consumes, affinities, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Object"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_object", "summary": "Create or replace an object by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Object"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Object"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ObjectWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_object", "summary": "Update part of an object", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Object"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Object"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ObjectPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_object", "summary": "Delete an object", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object/{id}/links/{field}": {"post": {"operationId": "link_object", "summary": "Add or remove ids on one of an object's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["materials", "technology", "effects", "abilities", "consumes", "affinities"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Object"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Object"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Object"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object/{id}/files": {"get": {"operationId": "list_object_files", "summary": "List a object's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Object"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_object_file_ticket", "summary": "Get a ticket to attach a file to a object", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Object"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object/{id}/files/{file_id}/download": {"get": {"operationId": "get_object_file_download", "summary": "Get a download link for a object's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Object"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object/{id}/files/{file_id}": {"delete": {"operationId": "delete_object_file", "summary": "Remove a object's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Object"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/object/{id}/hidden": {"put": {"operationId": "hide_object", "summary": "Hide a object", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Object"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_object", "summary": "Unhide a object", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The object's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Object"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon": {"get": {"operationId": "list_phenomenon", "summary": "List phenomenon elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["system", "catalysts", "empowerments", "triggers", "wielders", "environments"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, expression, effects, mythology, duration, system, catalysts, empowerments, triggers, wielders, environments, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhenomenonList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_phenomenon", "summary": "Create a phenomenon", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Phenomenon"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhenomenonWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon/{id}": {"get": {"operationId": "get_phenomenon", "summary": "Get one phenomenon", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["system", "catalysts", "empowerments", "triggers", "wielders", "environments"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, expression, effects, mythology, duration, system, catalysts, empowerments, triggers, wielders, environments, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Phenomenon"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_phenomenon", "summary": "Create or replace a phenomenon by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Phenomenon"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Phenomenon"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhenomenonWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_phenomenon", "summary": "Update part of a phenomenon", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Phenomenon"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Phenomenon"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhenomenonPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_phenomenon", "summary": "Delete a phenomenon", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon/{id}/links/{field}": {"post": {"operationId": "link_phenomenon", "summary": "Add or remove ids on one of a phenomenon's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["catalysts", "empowerments", "triggers", "wielders", "environments"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Phenomenon"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Phenomenon"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Phenomenon"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon/{id}/files": {"get": {"operationId": "list_phenomenon_files", "summary": "List a phenomenon's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Phenomenon"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_phenomenon_file_ticket", "summary": "Get a ticket to attach a file to a phenomenon", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Phenomenon"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon/{id}/files/{file_id}/download": {"get": {"operationId": "get_phenomenon_file_download", "summary": "Get a download link for a phenomenon's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Phenomenon"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon/{id}/files/{file_id}": {"delete": {"operationId": "delete_phenomenon_file", "summary": "Remove a phenomenon's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Phenomenon"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/phenomenon/{id}/hidden": {"put": {"operationId": "hide_phenomenon", "summary": "Hide a phenomenon", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Phenomenon"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_phenomenon", "summary": "Unhide a phenomenon", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The phenomenon's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Phenomenon"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin": {"get": {"operationId": "list_pin", "summary": "List pin elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["map", "element", "element_type", "element_id"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, x, y, z, map, element_type, element_id, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PinList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_pin", "summary": "Create a pin", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pin"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PinWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin/{id}": {"get": {"operationId": "get_pin", "summary": "Get one pin", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["map", "element", "element_type", "element_id"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, x, y, z, map, element_type, element_id, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pin"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_pin", "summary": "Create or replace a pin by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pin"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pin"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PinWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_pin", "summary": "Update part of a pin", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pin"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Pin"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PinPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_pin", "summary": "Delete a pin", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin/{id}/links/{field}": {"post": {"operationId": "link_pin", "summary": "Add or remove ids on one of a pin's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string"}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pin"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Pin"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Pin"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin/{id}/files": {"get": {"operationId": "list_pin_files", "summary": "List a pin's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Pin"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_pin_file_ticket", "summary": "Get a ticket to attach a file to a pin", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Pin"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin/{id}/files/{file_id}/download": {"get": {"operationId": "get_pin_file_download", "summary": "Get a download link for a pin's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Pin"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin/{id}/files/{file_id}": {"delete": {"operationId": "delete_pin_file", "summary": "Remove a pin's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Pin"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/pin/{id}/hidden": {"put": {"operationId": "hide_pin", "summary": "Hide a pin", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Pin"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_pin", "summary": "Unhide a pin", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The pin's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Pin"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation": {"get": {"operationId": "list_relation", "summary": "List relation elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["actor", "events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "narratives"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, background, start_date, end_date, intensity, actor, events, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, titles, constructs, narratives, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "characters", "required": false, "schema": {"type": "string", "format": "uuid"}, "description": "A Character id: elements whose `characters` link contains it. A malformed id is a 422 (param characters)."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RelationList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_relation", "summary": "Create a relation", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Relation"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RelationWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation/{id}": {"get": {"operationId": "get_relation", "summary": "Get one relation", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["actor", "events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "narratives"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, background, start_date, end_date, intensity, actor, events, characters, objects, locations, species, creatures, institutions, traits, collectives, zones, abilities, phenomena, languages, families, titles, constructs, narratives, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Relation"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_relation", "summary": "Create or replace a relation by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Relation"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Relation"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RelationWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_relation", "summary": "Update part of a relation", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Relation"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Relation"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RelationPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_relation", "summary": "Delete a relation", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation/{id}/links/{field}": {"post": {"operationId": "link_relation", "summary": "Add or remove ids on one of a relation's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "narratives"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Relation"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Relation"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Relation"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation/{id}/files": {"get": {"operationId": "list_relation_files", "summary": "List a relation's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Relation"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_relation_file_ticket", "summary": "Get a ticket to attach a file to a relation", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Relation"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation/{id}/files/{file_id}/download": {"get": {"operationId": "get_relation_file_download", "summary": "Get a download link for a relation's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Relation"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation/{id}/files/{file_id}": {"delete": {"operationId": "delete_relation_file", "summary": "Remove a relation's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Relation"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/relation/{id}/hidden": {"put": {"operationId": "hide_relation", "summary": "Hide a relation", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Relation"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_relation", "summary": "Unhide a relation", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The relation's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Relation"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species": {"get": {"operationId": "list_species", "summary": "List species elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_species", "nourishment", "reproduction", "adaptations", "traits", "locations", "zones", "affinities"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, appearance, instincts, sociality, temperament, communication, role, life_span, weight, aggression, parent_species, nourishment, reproduction, adaptations, traits, locations, zones, affinities, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpeciesList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_species", "summary": "Create a species", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Species"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpeciesWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species/{id}": {"get": {"operationId": "get_species", "summary": "Get one species", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["parent_species", "nourishment", "reproduction", "adaptations", "traits", "locations", "zones", "affinities"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, appearance, instincts, sociality, temperament, communication, role, life_span, weight, aggression, parent_species, nourishment, reproduction, adaptations, traits, locations, zones, affinities, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Species"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_species", "summary": "Create or replace a species by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Species"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Species"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpeciesWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_species", "summary": "Update part of a species", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Species"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Species"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SpeciesPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_species", "summary": "Delete a species", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species/{id}/links/{field}": {"post": {"operationId": "link_species", "summary": "Add or remove ids on one of a species's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["nourishment", "reproduction", "adaptations", "traits", "locations", "zones", "affinities"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Species"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Species"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Species"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species/{id}/files": {"get": {"operationId": "list_species_files", "summary": "List a species's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Species"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_species_file_ticket", "summary": "Get a ticket to attach a file to a species", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Species"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species/{id}/files/{file_id}/download": {"get": {"operationId": "get_species_file_download", "summary": "Get a download link for a species's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Species"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species/{id}/files/{file_id}": {"delete": {"operationId": "delete_species_file", "summary": "Remove a species's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Species"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/species/{id}/hidden": {"put": {"operationId": "hide_species", "summary": "Hide a species", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Species"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_species", "summary": "Unhide a species", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The species's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Species"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title": {"get": {"operationId": "list_title", "summary": "List title elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["issuer", "body", "superior_title", "holders", "symbols", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, authority, eligibility, status, history, grant_date, revoke_date, issuer, body, superior_title, holders, symbols, characters, institutions, families, zones, locations, objects, constructs, laws, collectives, creatures, phenomena, species, languages, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "characters", "required": false, "schema": {"type": "string", "format": "uuid"}, "description": "A Character id: elements whose `characters` link contains it. A malformed id is a 422 (param characters)."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TitleList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_title", "summary": "Create a title", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Title"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TitleWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title/{id}": {"get": {"operationId": "get_title", "summary": "Get one title", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["issuer", "body", "superior_title", "holders", "symbols", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, authority, eligibility, status, history, grant_date, revoke_date, issuer, body, superior_title, holders, symbols, characters, institutions, families, zones, locations, objects, constructs, laws, collectives, creatures, phenomena, species, languages, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Title"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_title", "summary": "Create or replace a title by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Title"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Title"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TitleWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_title", "summary": "Update part of a title", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Title"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Title"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TitlePatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_title", "summary": "Delete a title", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title/{id}/links/{field}": {"post": {"operationId": "link_title", "summary": "Add or remove ids on one of a title's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["holders", "symbols", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Title"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Title"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Title"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title/{id}/files": {"get": {"operationId": "list_title_files", "summary": "List a title's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Title"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_title_file_ticket", "summary": "Get a ticket to attach a file to a title", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Title"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title/{id}/files/{file_id}/download": {"get": {"operationId": "get_title_file_download", "summary": "Get a download link for a title's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Title"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title/{id}/files/{file_id}": {"delete": {"operationId": "delete_title_file", "summary": "Remove a title's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Title"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/title/{id}/hidden": {"put": {"operationId": "hide_title", "summary": "Hide a title", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Title"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_title", "summary": "Unhide a title", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The title's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Title"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait": {"get": {"operationId": "list_trait", "summary": "List trait elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["anti_trait", "empowered_abilities"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, social_effects, physical_effects, functional_effects, personality_effects, behaviour_effects, significance, charisma, coercion, competence, compassion, creativity, courage, anti_trait, empowered_abilities, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TraitList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_trait", "summary": "Create a trait", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Trait"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TraitWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait/{id}": {"get": {"operationId": "get_trait", "summary": "Get one trait", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["anti_trait", "empowered_abilities"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, social_effects, physical_effects, functional_effects, personality_effects, behaviour_effects, significance, charisma, coercion, competence, compassion, creativity, courage, anti_trait, empowered_abilities, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Trait"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_trait", "summary": "Create or replace a trait by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Trait"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Trait"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TraitWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_trait", "summary": "Update part of a trait", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Trait"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Trait"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TraitPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_trait", "summary": "Delete a trait", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait/{id}/links/{field}": {"post": {"operationId": "link_trait", "summary": "Add or remove ids on one of a trait's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["empowered_abilities"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Trait"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Trait"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Trait"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait/{id}/files": {"get": {"operationId": "list_trait_files", "summary": "List a trait's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Trait"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_trait_file_ticket", "summary": "Get a ticket to attach a file to a trait", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Trait"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait/{id}/files/{file_id}/download": {"get": {"operationId": "get_trait_file_download", "summary": "Get a download link for a trait's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Trait"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait/{id}/files/{file_id}": {"delete": {"operationId": "delete_trait_file", "summary": "Remove a trait's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Trait"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/trait/{id}/hidden": {"put": {"operationId": "hide_trait", "summary": "Hide a trait", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Trait"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_trait", "summary": "Unhide a trait", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The trait's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Trait"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone": {"get": {"operationId": "list_zone", "summary": "List zone elements", "parameters": [{"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 100}, "description": "Page size; clamped to 1-1000 (/changes refuses over 1000 instead). A non-integer is a 422."}, {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}, "description": "Opaque: the `next_cursor` of the previous page."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["phenomena", "linked_zones", "populations", "titles", "principles"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, role, context, start_date, end_date, phenomena, linked_zones, populations, titles, principles, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "name", "required": false, "schema": {"type": "string"}, "description": "Exact match on name."}, {"in": "query", "name": "name__icontains", "required": false, "schema": {"type": "string"}, "description": "Case-insensitive substring match on name."}, {"in": "query", "name": "supertype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "subtype", "required": false, "schema": {"type": "string"}, "description": "Exact match."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "A page, oldest change first (by change_seq, then id). With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ZoneList"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_zone", "summary": "Create a zone", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "Idempotent-Replay": {"description": "`true` when this response is a stored replay.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Zone"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Conflict: an element with this id already exists (id_conflict), or an Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after the Retry-After header's seconds).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "requestBody": {"required": true, "description": "The new element.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ZoneWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone/{id}": {"get": {"operationId": "get_zone", "summary": "Get one zone", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "query", "name": "expand", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string", "enum": ["phenomena", "linked_zones", "populations", "titles", "principles"]}}, "description": "Comma-separated link fields whose ids are replaced by ElementStub objects (one level deep). A field that is not a link is a 422."}, {"in": "query", "name": "fields", "required": false, "style": "form", "explode": false, "schema": {"type": "array", "items": {"type": "string"}}, "description": "Comma-separated sparse fieldset: only these keys are returned, so `type` never and `id` only when named. Accepts: id, name, description, supertype, subtype, image_url, role, context, start_date, end_date, phenomena, linked_zones, populations, titles, principles, created_at, updated_at, change_seq, created_by, changed_by, changed_via, or any extension field name. Anything else is a 422. With `expand`, a link is expanded only if it is also named here."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The element. With `?fields=` the body holds only the named keys (not this schema's required set); with `?expand=` each named link holds ElementStub objects instead of ids.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Zone"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Deleted: this world once held the element and deleted it (code gone, type not_found). The body carries the envelope and, beside it, the deletion's `change_seq` and `deleted_at`. An id never seen here, or deleted in another world, is a 404.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["change_seq", "deleted_at"], "properties": {"change_seq": {"type": "integer", "description": "The deletion's place in the world's change feed."}, "deleted_at": {"type": ["string", "null"], "format": "date-time", "description": "When it was deleted."}}}], "description": "The envelope plus the deletion's `change_seq` and `deleted_at`."}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "put": {"operationId": "put_zone", "summary": "Create or replace a zone by id", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Replaced: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Zone"}}}}, "201": {"description": "Created: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Zone"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The id is held by an element in another world (id_conflict).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "requestBody": {"required": true, "description": "The whole element; fields left out are reset to empty.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ZoneWrite"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_zone", "summary": "Update part of a zone", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Zone"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Zone"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "requestBody": {"required": true, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ZonePatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "delete_zone", "summary": "Delete a zone", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"204": {"description": "Deleted (or already absent).", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "description": "Idempotent: 204 whether or not the element existed. Its id is scrubbed from every other element's links.", "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone/{id}/links/{field}": {"post": {"operationId": "link_zone", "summary": "Add or remove ids on one of a zone's multi-link fields", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "path", "name": "field", "required": true, "schema": {"type": "string", "enum": ["phenomena", "linked_zones", "populations", "titles", "principles"]}, "description": "A multi-link field of this type (anything else is a 422)."}, {"in": "header", "name": "If-Match", "required": false, "schema": {"type": "string"}, "description": "Apply only if the element's `change_seq` is still this number (quoted or bare; `*` or no header means unconditional). When the element has moved, 412 precondition_failed with the live element as `current` beside the envelope; nothing is written and nothing is merged."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Updated: the full element.", "headers": {"X-OW-Schema-Version": {"description": "The canonical schema version this reply is shaped by (the standard's VERSION, e.g. 00.31.00); it moves when the standard does, not with the API's own v2.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Zone"}}}}, "400": {"description": "A link names an element that does not exist in this world (code invalid_link).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "412": {"description": "If-Match named a change_seq the element has moved past (precondition_failed); nothing was written. The body carries the envelope and, beside it, `current`: the element as it is now.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Error"}, {"type": "object", "required": ["current"], "properties": {"current": {"$ref": "#/components/schemas/Zone"}}}], "description": "The envelope plus `current`, the element as it is now."}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Zone"], "requestBody": {"required": true, "description": "The ids to add and remove.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LinkOperation"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone/{id}/files": {"get": {"operationId": "list_zone_files", "summary": "List a zone's files", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The element's files.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element: a file is seen by whoever may see its element (a guest or player key sees exactly the elements it sees). Newest first. Files never ride on the element: nothing here changes its body, its change_seq or /changes. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Zone"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "post": {"operationId": "create_zone_file_ticket", "summary": "Get a ticket to attach a file to a zone", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"201": {"description": "A ticket for one file upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "description": "The image lane's ticket shape, with kind `file` and the file's id.", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as Authorization: Bearer <ticket>, the file's raw bytes as the body."}, "upload_url": {"type": "string"}, "prefix": {"type": "string", "description": "w/<world_id>/<element_id>/"}, "key": {"type": "string", "description": "The object key the file lands under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: 25 MB, or what the world has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes)."}, "uses": {"type": "integer", "enum": [1]}, "issued_to": {"type": "object", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}, "kind": {"type": "string", "enum": ["file"]}, "file_id": {"type": "string", "format": "uuid"}}, "required": ["ticket", "upload_url", "prefix", "key", "max_bytes", "exp", "uses", "issued_to", "kind", "file_id"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a contributor or guest key changed, replaced, relinked or deleted an element someone else created (code not_author); those members change only their own, and, for an agent seat the owner gave a Character, that Character (never deleting it). Or the world's files have no room for this one (code files_full, type permission_error); the message gives the world's use, its cap and this file's size.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "413": {"description": "The file is over 25 MB (code file_too_large, type invalid_request, param size); the message gives both sizes.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {name, kind, size, label?}: an unknown kind (the message names the kinds and, for a Word or spreadsheet file, what to export it as), a name without its kind's extension, a size that is not a whole number of bytes, or a label over 80 characters (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world's daily file tickets are used up (code quota_exceeded). Both carry Retry-After. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN that may change the element (the write rule of PATCH; an agent seat answers as its sponsor). Body: the file's name, kind and size, and an optional label. Returns a single-use ticket in the image lane's shape with kind `file`: send the bytes to upload_url with it. The file is listed once the upload host has checked and stored it. Kinds: text (txt, md, json, csv, yaml, toml), pdf, and zip only as a world's own bundle. At most 25 MB a file; a world holds 250 MB of files; 200 tickets a world a day. This route takes no Idempotency-Key: the single-use ticket is the idempotency (a second POST is a second ticket). Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Zone"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The file's name, ending in its kind's extension (.yml and .markdown are taken too)."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "minimum": 1, "description": "Bytes."}, "label": {"type": ["string", "null"], "maxLength": 80}}, "required": ["name", "kind", "size"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone/{id}/files/{file_id}/download": {"get": {"operationId": "get_zone_file_download", "summary": "Get a download link for a zone's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"200": {"description": "The link. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "description": "The file on the download host; it answers as an attachment, CORS open, ranges honoured. The link is the credential: it lives 5 minutes and may be fetched more than once."}, "exp": {"type": "integer", "description": "Unix seconds after which the link is refused."}, "name": {"type": "string"}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer"}}, "required": ["url", "exp", "name", "kind", "size"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key that may read the element. A link on the download host that lives 5 minutes; the file comes as an attachment, never shown inside a page. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Zone"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone/{id}/files/{file_id}": {"delete": {"operationId": "delete_zone_file", "summary": "Remove a zone's file", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}, {"in": "path", "name": "file_id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The file's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Removed.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is neither the world's owner's nor the file's uploader's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or the key of whoever added the file (an agent seat answers as its sponsor). The file is removed for real: it leaves the lists and the world's total at once, and its bytes are deleted from the store. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Zone"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/zone/{id}/hidden": {"put": {"operationId": "hide_zone", "summary": "Hide a zone", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The id is malformed, the body is not JSON, or it names anything but an empty fields list (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Marks the element hidden; hiding a hidden element again changes nothing. No Idempotency-Key: the call is idempotent. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Zone"], "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"fields": {"type": "array", "items": {"type": "string"}, "maxItems": 0, "description": "Reserved for hiding single fields, which is not built yet: leave it out or send []. A non-empty list is a 422."}}, "additionalProperties": false}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}, "delete": {"operationId": "unhide_zone", "summary": "Unhide a zone", "parameters": [{"in": "path", "name": "id", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The zone's id (a malformed id is a 422)."}], "responses": {"204": {"description": "Not hidden.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (a person's or a seat's), with its PIN. Clears the mark; an element that is not hidden stays as it is. The element's body and its change_seq do not change. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["Zone"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/changes": {"get": {"operationId": "get_changes", "summary": "Changes", "parameters": [{"in": "query", "name": "since", "required": false, "schema": {"type": "string"}, "description": "Where to start: the `cursor` of the previous page, passed back as is, or an integer (changes after that change_seq). Omit it to read from the beginning. Anything else is a 422. (The element lists page with `cursor`; this feed takes `since`.)"}, {"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 500, "minimum": 1, "maximum": 1000}, "description": "Page size, 1-1000. Over 1000 is a 422, not clamped."}, {"in": "query", "name": "head", "required": false, "schema": {"type": "boolean", "default": false}, "description": "`true`: only the feed's current position (`cursor` and `head`, no changes), to start following from now. `since` and `limit` are ignored."}], "responses": {"200": {"description": "A page of the change feed, oldest change first (by change_seq, then id).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChangesPage"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "A guest's view of this world changed since its cursor (or the cursor's shape is not this caller's: a guest's is seq:id:epoch, everyone else's seq:id): pull again from since=0 and keep what you hold that the new walk does not name (code resync_required). Where the server has the hidden mark switched on, a contributor's cursor is seq:id:epoch too, and the first call with a contributor's older seq:id cursor is this 409. There the envelope also says why, in `reason`: `role` (your own role or membership moved: keep what the new walk does not name) or `view` (what you may see moved: drop it); both since your cursor, `role`. A tool never empties a copy on its own.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Every write to the world, in order: each change is an `upsert` (with the\nelement) or a `delete`, with its type, id and `change_seq`. Pass the\nresponse's `cursor` back as `since` until `has_more` is false, then poll\nwith it to stay in sync. Any other query parameter is a 422.", "tags": ["Changes"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/world": {"get": {"operationId": "get_world", "summary": "World", "parameters": [{"in": "header", "name": "If-None-Match", "required": false, "schema": {"type": "string"}, "description": "The ETag of the world you hold: a match answers 304, no body."}], "responses": {"200": {"description": "The world.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/World"}}}, "headers": {"ETag": {"description": "The world's version tag. Send it back as If-None-Match to get a bodiless 304 while the world is unchanged.", "schema": {"type": "string"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "304": {"description": "Not modified (If-None-Match matched the ETag).", "headers": {"ETag": {"description": "The world's version tag. Send it back as If-None-Match to get a bodiless 304 while the world is unchanged.", "schema": {"type": "string"}}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world named by the credential. owner_character is the owner's \"this Character is me\" (a Character id, or null; null too when the owner hid that Character from this key).", "tags": ["World"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}, "patch": {"operationId": "patch_world", "summary": "Patch World", "parameters": [], "responses": {"200": {"description": "The updated world.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/World"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or a member key: only the world's owner can change the world's own settings (code owner_only), except that a co-builder may send time_range_current alone.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Unknown or read-only field, or a bad value (param names it). owner_character that is not a Character in this world: \"Not a Character in this world.\" (param owner_character).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Owner only, with one exception: a co-builder may send time_range_current alone (moving the world's now). Writable: name, description, image_url, time_basic_unit, time_format_names, time_format_equivalents, time_range_min, time_range_max, time_range_current, length_unit, mass_unit, distance_unit (free-text unit labels, at most 64 characters, trimmed; \"\" clears), and owner_character (a Character id in this world, or null: the owner's \"this Character is me\", shown by GET /members and GET /me).", "tags": ["World"], "requestBody": {"required": false, "description": "Only the fields to change.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorldPatch"}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/bulk": {"post": {"operationId": "bulk_write", "summary": "Bulk", "parameters": [{"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string", "maxLength": 200}, "description": "Replays of the same key with the same body return the first successful response (with `Idempotent-Replay: true`) for 24h; the same key with a different body is a 409 idempotency_error. While the first request with a key is still running, the same key is a 409 idempotency_in_flight: retry after Retry-After."}, {"in": "header", "name": "X-OW-Agent", "required": false, "schema": {"type": "string"}, "description": "Send it (any non-empty value) when an agent makes this write: the change is recorded with `changed_via` `declared` (a delete's tombstone with `deleted_via`). An agent seat's key and MCP are recorded as agent-made without it. The value is never stored."}], "responses": {"200": {"description": "Always 200 once the batch is read: per-item statuses in `items`, and `errors` is true when any item failed. A contributor or guest's item on an element someone else created fails 403 not_author.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkResult"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "An Idempotency-Key was reused with a different body (idempotency_error), or a request with this Idempotency-Key is still in flight (idempotency_in_flight; retry after Retry-After).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Many writes of mixed types in one request. Each item succeeds or fails on\nits own (unless `atomic`); links between items in the batch resolve.", "tags": ["Bulk"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["items"], "properties": {"items": {"type": "array", "maxItems": 1000, "description": "Up to 1000 writes of any types. An item with an `id` creates or replaces that element; without one, it creates. Items may link to each other in the same batch.", "items": {"type": "object", "required": ["type", "element"], "properties": {"type": {"type": "string", "description": "The element type, e.g. character."}, "element": {"type": "object", "description": "The element's fields, as for a create."}}}}, "atomic": {"type": "boolean", "default": false, "description": "`true`: if any item fails, nothing is written. The response still lists each item as it would have gone."}}}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/search": {"get": {"operationId": "search_elements", "summary": "Search a world's elements by the words in their text", "parameters": [{"in": "query", "name": "q", "required": true, "schema": {"type": "string", "minLength": 2, "maxLength": 200}, "description": "The words to find, 2 to 200 characters once trimmed. A word is a run of letters, digits or underscores; anything else separates words."}, {"in": "query", "name": "type", "required": false, "schema": {"type": "string", "enum": ["ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"]}, "description": "Search one type only. An unknown type is a 422."}, {"in": "query", "name": "limit", "required": false, "schema": {"type": "integer", "default": 50}, "description": "How many results at most; clamped to 1-200. A non-integer is a 422."}, {"in": "query", "name": "as", "required": false, "schema": {"type": "string"}, "description": "View as (the owner's keys only: a key with no member): read as another reader would. A membership id of this world (a person, an agent seat or a player seat) reads as that member's own key, its own creations included; a Character id reads as a guest of that Character with no membership (what it knows, the roster, nothing counted as its own); `public` reads everything that is not hidden. A hidden element is the plain 404, never a 410. An unknown value is a 422 (param as); a member key sending it is a 403 view_as_owner_only. Writes, /changes, /members, /me, the world and the files routes do not take it (422)."}], "responses": {"200": {"description": "The matching elements, name matches first.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchResults"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "A member key sent `as`: view as is the owner's (view_as_owner_only).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request (invalid_request): `q` missing, under 2 or over 200 characters, or holding no word; an unknown `type`; a `limit` that is not an integer; or a query parameter other than q, type, limit. `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Every element of the world whose `name` or text fields (`description` and the type's other prose fields, such as a Character's `background`; not supertype, subtype or image_url) hold every word of `q` as a whole word, ignoring case: `ore` does not find `more`, and `copper mine` needs both words somewhere in the element. No stemming. A guest key searches only what it may read.", "tags": ["Search"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/account/ask": {"post": {"operationId": "ask", "summary": "Ask", "parameters": [], "responses": {"200": {"description": "The answer: `answer` (text; \"The world doesn't say.\" when the elements do not hold it), `model`, `stop_reason`, `usage` {input_tokens, output_tokens}, `remaining` (how many more calls of this one's size fit in what this account has left today), `tokens_remaining` (the account's tokens left today after this call) and `token_rating` (the account's daily token rating).", "content": {"application/json": {"schema": {"type": "object", "properties": {"answer": {"type": "string"}, "model": {"type": "string"}, "stop_reason": {"type": "string"}, "usage": {"type": "object", "properties": {"input_tokens": {"type": "integer"}, "output_tokens": {"type": "integer"}}, "required": ["input_tokens", "output_tokens"]}, "remaining": {"type": "integer", "description": "How many more calls of this one's size fit in what this account has left today."}, "tokens_remaining": {"type": "integer", "description": "The account's tokens left today after this call."}, "token_rating": {"type": "integer", "description": "The account's daily token rating."}}, "required": ["answer", "model", "stop_reason", "usage", "remaining", "tokens_remaining", "token_rating"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "The world is not one this account owns or is a member of (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "world_id, question (1 to 500 characters) or context (1 to 8 non-empty strings, each at most 2000 characters, 12000 in all) is missing or malformed; `param` names it.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "This account has used today's token rating (quota_exceeded): fewer tokens are left than an answer may take. Retry-After says when the rating resets (midnight UTC). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "502": {"description": "The model gave no answer (ask_failed); nothing was counted.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "503": {"description": "Asking is not switched on, or is paused for the day (ask_unavailable), nothing counted; or every request slot stayed full (server_busy, with Retry-After).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "One question about a world, answered from the elements the client sends\n(the ones its own search found, as text), by a model the platform pays for\nand meters against the account's daily token rating. Body: `world_id`,\n`question`, `context` (a list of elements as text). The model answers only from the context and\nsays so when it does not hold the answer. An account credential (Bearer\n`ow_a_`, a signed-in session, or a phone's `X-Session-Token`).", "tags": ["Account"], "requestBody": {"required": true, "description": "The world, the question, and the elements the client's own search found, as text.", "content": {"application/json": {"schema": {"type": "object", "properties": {"world_id": {"type": "string", "format": "uuid", "description": "A world this account owns or is a member of."}, "question": {"type": "string", "minLength": 1, "maxLength": 500}, "context": {"type": "array", "minItems": 1, "maxItems": 8, "items": {"type": "string", "minLength": 1, "maxLength": 2000}, "description": "One element as text per item, 12000 characters in all."}}, "required": ["world_id", "question", "context"]}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/sort": {"post": {"operationId": "sort_notes", "summary": "Sort Notes", "parameters": [], "responses": {"200": {"description": "The proposed changes: `rows`, each one of four kinds (`make` a new element; `add_to` text appended to a field of a slice element; `link` one slice element to another through a field; `ask` a question with `candidates` when a name matches several slice elements or the sorter is unsure, and optionally `then`, the add_to or link row it stands for without its target), each with the `source_line` it came from; `not_proposed` (lines not placed, each with a `reason`); `model`, `stop_reason`, `usage` {input_tokens, output_tokens}, `remaining` (how many more calls of this one's size fit in what this account has left today), `tokens_remaining` (the account's tokens left today after this call) and `token_rating` (the account's daily token rating). Every id in a row is an id from the request's slice; a row naming any other id is moved to not_proposed. Names, types and anchors of the elements a row points at are the slice's own. Nothing is written to the world.", "content": {"application/json": {"schema": {"type": "object", "properties": {"rows": {"type": "array", "items": {"type": "object", "description": "One proposed change. `kind` says which fields it carries: `make` (proposal {type, name, description}); `add_to` (target, field, op, value); `link` (target, field, value_id, value_name); `ask` (question, candidates, and optionally then, an add_to or link row without its target).", "properties": {"kind": {"type": "string", "enum": ["make", "add_to", "link", "ask"]}, "source_line": {"type": "string"}, "proposal": {"type": "object", "properties": {"type": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}}, "required": ["type", "name", "description"]}, "target": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "type": {"type": "string"}, "anchor": {"type": "string"}}, "required": ["id", "name", "type"]}, "field": {"type": "string"}, "op": {"type": "string", "enum": ["append"]}, "value": {"type": "string"}, "value_id": {"type": "string", "format": "uuid"}, "value_name": {"type": "string"}, "question": {"type": "string"}, "candidates": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "type": {"type": "string"}, "anchor": {"type": "string"}}, "required": ["id", "name", "type"]}}, "then": {"type": "object", "properties": {"kind": {"type": "string", "enum": ["add_to", "link"]}, "field": {"type": "string"}, "op": {"type": "string", "enum": ["append"]}, "value": {"type": "string"}, "value_id": {"type": "string", "format": "uuid"}, "value_name": {"type": "string"}}, "required": ["kind", "field"]}}, "required": ["kind", "source_line"]}}, "not_proposed": {"type": "array", "items": {"type": "object", "properties": {"source_line": {"type": "string"}, "reason": {"type": "string"}}, "required": ["source_line", "reason"]}}, "model": {"type": "string"}, "stop_reason": {"type": "string"}, "usage": {"type": "object", "properties": {"input_tokens": {"type": "integer"}, "output_tokens": {"type": "integer"}}, "required": ["input_tokens", "output_tokens"]}, "remaining": {"type": "integer", "description": "How many more calls of this one's size fit in what this account has left today."}, "tokens_remaining": {"type": "integer", "description": "The account's tokens left today after this call."}, "token_rating": {"type": "integer", "description": "The account's daily token rating."}}, "required": ["rows", "not_proposed", "model", "stop_reason", "usage", "remaining", "tokens_remaining", "token_rating"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "The world is not one this account owns or is a member of (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "world_id, notes or slice is missing or malformed; `param` names it. notes: 1 to 8, each a string or an object {text, image_url} with at least one of the two; text at most 4000 characters a note, 16000 in all; at most 4 images, each an https://media.onlyworlds.com/ URL under this world's upload prefix u/<world_id>/. slice: 0 to 100 objects, each with a UUID id, one of the 22 element types in lowercase, a name, and an optional anchor of at most 120 characters.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "This account has used today's token rating (quota_exceeded): fewer tokens are left than a sort may take (a sort draws on the same rating as ask). Retry-After says when the rating resets (midnight UTC). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "502": {"description": "The model gave no usable answer (ask_failed); nothing was counted.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "503": {"description": "Sorting is not switched on, or is paused for the day (ask_unavailable), nothing counted; or every request slot stayed full (server_busy, with Retry-After).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Sort a user's unsorted notes into proposed changes to a world: new\nelements, text to add, links to make, and questions where the notes are\nambiguous. Body: `world_id`, `notes` (a list of notes: text, or a\nphotographed note uploaded to this world's media, with optional text) and `slice`\n(the elements the client's own search found for them: id, type, name and an\noptional anchor, a short phrase that tells two same-named elements apart).\nThe answer only proposes; the client applies what the user accepts. A sort\nspends its tokens from the account's daily token rating, as ask does. An account\ncredential (Bearer `ow_a_`, a signed-in session, or a phone's\n`X-Session-Token`).", "tags": ["Account"], "requestBody": {"required": true, "description": "The world, the notes to sort, and the elements the client's own search found for them.", "content": {"application/json": {"schema": {"type": "object", "properties": {"world_id": {"type": "string", "format": "uuid", "description": "A world this account owns or is a member of."}, "notes": {"type": "array", "minItems": 1, "maxItems": 8, "items": {"oneOf": [{"type": "string", "minLength": 1, "maxLength": 4000}, {"type": "object", "description": "A note with a photograph: text (may be empty) and image_url, an image uploaded into this world (https://media.onlyworlds.com/u/<world_id>/...). At least one of the two.", "properties": {"text": {"type": "string", "maxLength": 4000}, "image_url": {"type": "string", "format": "uri"}}}]}, "description": "16000 characters of text in all, at most 4 images."}, "slice": {"type": "array", "maxItems": 100, "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "type": {"type": "string", "enum": ["ability", "character", "collective", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"]}, "name": {"type": "string", "minLength": 1, "maxLength": 200}, "anchor": {"type": "string", "maxLength": 120, "description": "A short phrase that tells two same-named elements apart."}}, "required": ["id", "type", "name"]}}}, "required": ["world_id", "notes"]}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/allowance": {"get": {"operationId": "get_allowance", "summary": "Read this account's daily token rating", "parameters": [], "responses": {"200": {"description": "The account's token rating today: `token_rating` (tokens a day), `tokens_used_today`, `tokens_remaining` and `resets_at` (ISO 8601, UTC: the next midnight).", "content": {"application/json": {"schema": {"type": "object", "properties": {"token_rating": {"type": "integer", "description": "The account's daily token rating."}, "tokens_used_today": {"type": "integer", "description": "Tokens spent today (UTC); may pass the rating by the size of the last call."}, "tokens_remaining": {"type": "integer", "description": "Tokens left today, never below 0."}, "resets_at": {"type": "string", "format": "date-time", "description": "When the token rating resets: the next midnight UTC."}}, "required": ["token_rating", "tokens_used_today", "tokens_remaining", "resets_at"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "How much of its daily token rating this account has left: asking and\nsorting both draw on it, counted in tokens. Reading it spends nothing. An\naccount credential (Bearer `ow_a_`, a signed-in session, or a phone's\n`X-Session-Token`).", "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account": {"get": {"operationId": "get_profile", "summary": "Get Profile", "parameters": [], "responses": {"200": {"description": "The profile.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "patch": {"operationId": "patch_profile", "summary": "Patch Profile", "parameters": [], "responses": {"200": {"description": "The updated profile.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds": {"get": {"operationId": "list_worlds", "summary": "List Worlds", "parameters": [], "responses": {"200": {"description": "Owned and shared worlds.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "post": {"operationId": "create_world", "summary": "Create World", "parameters": [], "responses": {"201": {"description": "Created: the world and its write key (shown once).", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Body `{name, pin?, key_name?}`. An account without a PIN must send `pin`\n(1000-9999), which becomes its account PIN; an account with one must omit\nit (422). Returns the world and an `ow_w_` key, shown once; `key_name`\nnames that key.", "tags": ["Account"], "requestBody": {"required": true, "description": "The world's name; the account PIN, with a first world only; and, optionally, a name for the write key the world is made with.", "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 255, "description": "The world's name (not blank)."}, "pin": {"type": ["string", "integer"], "description": "Only when the account has no PIN yet: 4 digits, 1000-9999. It becomes the account PIN and the write wall on every world of the account. An account with a PIN omits it."}, "key_name": {"type": ["string", "null"], "minLength": 1, "maxLength": 80, "description": "A name for the world's write key, as the website's key list shows it (1 to 80 characters, not blank; trimmed). Omitted or null: the key has no name. The route makes exactly one key either way."}}, "required": ["name"]}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/keys": {"post": {"operationId": "mint_world_key", "summary": "Mint World Key", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created: the key (shown once).", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "A member without an account PIN asked for a write key (pin_required).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "get": {"operationId": "list_world_keys", "summary": "List World Keys", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "The world's keys (last4 only).", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: an unknown or malformed query parameter, cursor or id (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/keys/{key_id}": {"delete": {"operationId": "revoke_world_key", "summary": "Revoke World Key", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "key_id", "schema": {"title": "Key Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Revoked.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/invites": {"post": {"operationId": "create_invite", "summary": "Create Invite", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Invited. The body is the same whether or not an account uses that email.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "That email already belongs to a member (already_member).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Invite an email as a member (owner only). Always the same 201 body shape,\nwhether or not an account exists for the email (no account enumeration).", "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/invites/{invite_id}": {"delete": {"operationId": "cancel_invite", "summary": "Cancel Invite", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "invite_id", "schema": {"title": "Invite Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Invite cancelled.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/members": {"get": {"operationId": "list_account_world_members", "summary": "List Members", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "Members and pending invites. An agent seat's row adds kind, agent_name, for_email, sponsor and may_change_character; a player seat's adds kind (player) and name; a person's row has none of them. One Character can be on two rows (roster_character): a member's, and their AI's seat that plays it.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccountMembers"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's members, agent seats among them, and its pending email\ninvites (owner only).", "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/members/{member_id}": {"delete": {"operationId": "remove_member", "summary": "Remove Member", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "member_id", "schema": {"title": "Member Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Member removed; their keys are deleted.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "patch": {"operationId": "change_member_role", "summary": "Change Member Role", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "member_id", "schema": {"title": "Member Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "The member's row as GET /members shows it, with the new role and the Character it plays (roster_character).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccountMember"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A field is out of range (invalid_request, param names it): an unknown role, no role and no character, character on a person's row, a null character on a player seat, or a character that is not a Character in this world. A role sent for a player seat is player_role_fixed (param role): a player is a guest, fixed. A character the world hides is character_hidden (param character): a played Character is on every reader's roster, so a hidden one can never be picked.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Change a member's role among co_builder, contributor and guest, and, for\nan agent or a player seat, the Character it plays (owner only). The role applies from\nthe member's next request. A member made a guest keeps its roster Character\nonly if it created it; a Character given in the same request is set after\nthe role, so it stays. Both change together or neither does.", "tags": ["Account"], "requestBody": {"required": false, "description": "What to change: the member's role, an agent's or a player's Character, or both.", "content": {"application/json": {"schema": {"type": "object", "properties": {"role": {"type": "string", "enum": ["co_builder", "contributor", "guest"], "description": "The member's new role, from their next request."}, "character": {"type": ["string", "null"], "format": "uuid", "description": "An agent seat or a player seat (a person's row is 422 for now): the Character it plays, any Character in this world, or null for none. On an agent seat it is your grant: the seat can change this Character, never delete it, even when someone else made it, and a guest seat sees what it knows; null takes the grant back. With the world's agent narrator rule on, the seat narrates as it. On a player seat it is the Character the player reads as, never a grant (a player writes nothing), and never null: to end the seat, remove it. Setting it never touches the Character itself."}}}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/seats": {"post": {"operationId": "create_world_seat", "summary": "Create World Seat", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "The seat, its Character, and its key and secret with the .env lines, shown this once: the server keeps only a hash of the secret. Lost the answer? GET /seats lists the seat without its key, and POST /seats/{seat_id}/key gives it a new key and secret: it keeps its role, its Character and its work.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SeatCreated"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not a world this account owns or is a member of (not_found): a removed member gets the same answer as a stranger.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A field is out of range (invalid_request, param names it: name, role, character), the role is above your own, character mine with no Character of yours, or a cap: 5 agents of yours in this world, or the world's 50 agents and unused agent links.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Make a seat for your own AI in a world you own or are a member of: its own\nmembership, write key and secret, no join link. You answer for it. Its role\nis at most your own (the owner may give any); it plays a new Character of its\nown, none, or yours (character mine). Its writes are marked as an agent's.\n\nNo Idempotency-Key: a stored replay would keep the key and secret, which are\nshown once. If the answer is lost, GET /seats shows the seat without its key,\nand POST /seats/{seat_id}/key gives it a new key and secret.", "tags": ["Account"], "requestBody": {"required": true, "description": "The AI's name, its role, and the Character it plays.", "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 80, "description": "The AI's own name in the world. Required."}, "role": {"type": "string", "enum": ["co_builder", "contributor", "guest"], "description": "At most your own role (co_builder > contributor > guest); the owner may give any. Default contributor, or your own role when that is lower."}, "character": {"type": "string", "enum": ["new", "none", "mine"], "description": "new (default): a new Character of its own, supertype Agent, named for it. none: no Character. mine (members only): it plays the Character you play here, and can change it only if you can (you made it, you are a co-builder, or the owner gave it to you)."}}, "required": ["name"]}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "get": {"operationId": "list_world_seats", "summary": "List World Seats", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "Live agent seats: a member sees the seats they answer for, the owner every seat in the world. Never a key or a secret.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SeatList"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not a world this account owns or is a member of (not_found): a removed member gets the same answer as a stranger.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The live agent seats you can see in a world: your own AI's seats, or, as\nthe owner, every seat. No keys or secrets: those are shown once, when a seat\nis made.", "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/seats/{seat_id}": {"delete": {"operationId": "remove_world_seat", "summary": "Remove World Seat", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "seat_id", "schema": {"title": "Seat Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Removed: the seat's key and secret stop working at once.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No live seat with this id in the world that you answer for or own (not_found): another member's seat gets the same answer.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Remove an agent seat: its sponsor or the world's owner may. Its keys are\ndeleted; a Character it made and nobody touched goes with it, any other\nstays.", "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/seats/{seat_id}/key": {"post": {"operationId": "rekey_world_seat", "summary": "Rekey World Seat", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "seat_id", "schema": {"title": "Seat Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "The seat's new key and secret with the .env lines, shown this once. The old key and the old secret stop working at once (the old key answers 401 key_revoked); the seat keeps its membership, its role, its Character and its work.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SeatCreated"}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No live seat with this id in the world that you answer for or own (not_found): another member's seat, a removed seat and a person's membership get the same answer.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Give an agent seat a new key and secret, for an AI that lost its own: the\nseat's sponsor or the world's owner may. The old key and the old secret stop\nat once; nothing else about the seat changes. No body.\n\nNo Idempotency-Key: a stored replay would keep the new key and secret, which\nare shown once. Lost this answer too? Ask for another new key.", "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/leave": {"post": {"operationId": "leave_world", "summary": "Leave World", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "You left the world; your keys for it are deleted.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/links": {"post": {"operationId": "create_world_link", "summary": "Create World Link", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "The link, with its code, the invite link and the line to send. The code is shown this once: the server keeps only a hash of it, so it can never be read again (list the links later without their codes, and revoke one by id).", "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid", "description": "The link; DELETE it to revoke."}, "reader": {"type": "string", "enum": ["person", "agent", "player"], "description": "Who the link is for: a person, who accepts it signed in (POST /api/v2/account/join); an AI agent, which redeems it for a seat (POST /api/v2/join); or a player, a friend with no account, who redeems it at POST /api/v2/join for a read key and reads as a guest."}, "role": {"type": "string", "enum": ["co_builder", "contributor", "guest"]}, "label": {"type": "string", "description": "The name the owner gave an agent link on the website, or empty."}, "inviter_name": {"type": "string", "description": "The name the link is signed with, or empty (the invite line then reads \"Someone\")."}, "with_character": {"type": "boolean", "description": "Whether redeeming gives the agent a Character of its own. Always false on a person link, always true on a player link."}, "preview": {"type": "boolean", "description": "Whether the link carries the world's id in its path (/join/{world_id}#code), so a link pasted in a chat unfurls with the world's name and picture; false for today's /join#code. Always false on an agent link."}, "last4": {"type": "string", "description": "The code's last four characters, to tell links apart."}, "created_at": {"type": "string", "format": "date-time"}, "expires_at": {"type": "string", "format": "date-time"}, "uses": {"type": "integer", "description": "How many times the link was used: 0 or 1 for a link for one; a table link counts its seats, removed ones included."}, "status": {"type": "string", "enum": ["pending", "used", "revoked", "expired"], "description": "pending (still works), used, revoked or expired."}, "character": {"type": ["object", "null"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": ["string", "null"]}}, "required": ["id", "name"], "description": "A player link only: the Character the player reads as, {id, name}; null while a link for a fresh Character is unused (the one made at the door once it is). name is null if the Character was deleted (the redeem then makes a fresh one)."}, "max_uses": {"type": "integer", "minimum": 2, "maximum": 50, "description": "A table link only: how many times the link works. status is pending while uses < max_uses (and the link is neither revoked nor expired), used once full."}, "characters": {"type": "array", "description": "A table link only: the Characters its players may pick, in the order you picked them; empty when each player gets a fresh Character named after them. taken: a live seat in the world plays it. name is null if the Character was deleted.", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": ["string", "null"]}, "taken": {"type": "boolean"}}, "required": ["id", "name", "taken"]}}, "code": {"type": "string", "description": "The ow_j_ join code. Shown in this answer only: the server keeps a hash, so it can never be read again."}, "link": {"type": "string", "format": "uri", "description": "The invite link: the /join page with the code after #; with preview on, /join/{world_id} with the code after #. The code is only ever in the fragment: read it from there, whatever the path."}, "line": {"type": "string", "description": "The one line to send: who invites, to which world, and the link."}}, "required": ["id", "reader", "role", "label", "inviter_name", "with_character", "preview", "last4", "created_at", "expires_at", "uses", "status", "code", "link", "line"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not a world this account owns (not_found): a member, even a co-builder, gets the same answer as a stranger.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A field is out of range (invalid_request, param names it: reader, role, days, inviter_name, with_character, character, preview, max_uses, characters), or the world is at its cap: 50 pending invites, person links and player links together, or 50 agents and unused agent links.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Make an invite link for a world this account owns: for a person, who\nopens it and accepts signed in; for an AI agent, which redeems it for a\nseat of its own; or for a player, a friend with no account, who reads as a\nguest playing one Character. A link works once, until it expires or is\nrevoked.", "tags": ["Account"], "requestBody": {"required": true, "description": "Who the link is for, and what it grants.", "content": {"application/json": {"schema": {"type": "object", "properties": {"reader": {"type": "string", "enum": ["person", "agent", "player"], "description": "person, agent or player. Required."}, "role": {"type": "string", "enum": ["co_builder", "contributor", "guest"], "description": "The role the link grants. Default guest for a person link, contributor for an agent link; a player link is guest, and refuses any other."}, "days": {"type": "integer", "enum": [1, 7, 30], "description": "Days until the link expires. Default 7."}, "inviter_name": {"type": "string", "maxLength": 80, "description": "The name to sign the link with."}, "with_character": {"type": "boolean", "description": "An agent link only: whether the agent gets a Character of its own. Default true; ignored on a person link."}, "character": {"type": "string", "description": "A player link only: the Character the player reads as, the id of a Character in this world, or new (the default) for a fresh one made when the link is used. Whoever opens the link first reads as it, until you remove them."}, "preview": {"type": "boolean", "description": "A person or player link only: true puts the world's id in the link's path (/join/{world_id}#code), so a link pasted in a chat app shows the world's name and picture. A chat app keeps that preview after you revoke the link. Default false: today's /join#code. True on an agent link is refused."}, "max_uses": {"type": "integer", "minimum": 1, "maximum": 50, "description": "A player link only: how many times the link works. Default 1, a link for one person. 2 to 50 makes a table: one link, each person who opens it picks one Character from `characters`, once, and reads as a guest with no account. A table counts once against the 50 pending invites; its seats count against the world's 50 players as each is made, so a full world refuses the pick, never the mint. Any other value, or the key on a person or agent link, is refused."}, "characters": {"type": "array", "maxItems": 50, "items": {"type": "string", "format": "uuid"}, "description": "A table only (max_uses 2 or more): the Characters its players may pick, ids of Characters in this world, in the order they are shown. A hidden Character is refused: it can never be picked. Empty or absent: each player names a fresh Character after themselves. A table takes no `character`."}}, "required": ["reader"]}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "get": {"operationId": "list_world_links", "summary": "List World Links", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "Every link made for the world, newest first, each with its state. No codes.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid", "description": "The link; DELETE it to revoke."}, "reader": {"type": "string", "enum": ["person", "agent", "player"], "description": "Who the link is for: a person, who accepts it signed in (POST /api/v2/account/join); an AI agent, which redeems it for a seat (POST /api/v2/join); or a player, a friend with no account, who redeems it at POST /api/v2/join for a read key and reads as a guest."}, "role": {"type": "string", "enum": ["co_builder", "contributor", "guest"]}, "label": {"type": "string", "description": "The name the owner gave an agent link on the website, or empty."}, "inviter_name": {"type": "string", "description": "The name the link is signed with, or empty (the invite line then reads \"Someone\")."}, "with_character": {"type": "boolean", "description": "Whether redeeming gives the agent a Character of its own. Always false on a person link, always true on a player link."}, "preview": {"type": "boolean", "description": "Whether the link carries the world's id in its path (/join/{world_id}#code), so a link pasted in a chat unfurls with the world's name and picture; false for today's /join#code. Always false on an agent link."}, "last4": {"type": "string", "description": "The code's last four characters, to tell links apart."}, "created_at": {"type": "string", "format": "date-time"}, "expires_at": {"type": "string", "format": "date-time"}, "uses": {"type": "integer", "description": "How many times the link was used: 0 or 1 for a link for one; a table link counts its seats, removed ones included."}, "status": {"type": "string", "enum": ["pending", "used", "revoked", "expired"], "description": "pending (still works), used, revoked or expired."}, "character": {"type": ["object", "null"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": ["string", "null"]}}, "required": ["id", "name"], "description": "A player link only: the Character the player reads as, {id, name}; null while a link for a fresh Character is unused (the one made at the door once it is). name is null if the Character was deleted (the redeem then makes a fresh one)."}, "max_uses": {"type": "integer", "minimum": 2, "maximum": 50, "description": "A table link only: how many times the link works. status is pending while uses < max_uses (and the link is neither revoked nor expired), used once full."}, "characters": {"type": "array", "description": "A table link only: the Characters its players may pick, in the order you picked them; empty when each player gets a fresh Character named after them. taken: a live seat in the world plays it. name is null if the Character was deleted.", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": ["string", "null"]}, "taken": {"type": "boolean"}}, "required": ["id", "name", "taken"]}}}, "required": ["id", "reader", "role", "label", "inviter_name", "with_character", "preview", "last4", "created_at", "expires_at", "uses", "status"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/worlds/{world_id}/links/{link_id}": {"delete": {"operationId": "revoke_world_link", "summary": "Revoke World Link", "parameters": [{"in": "path", "name": "world_id", "schema": {"title": "World Id", "type": "string"}, "required": true}, {"in": "path", "name": "link_id", "schema": {"title": "Link Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Revoked: the code no longer works. A used, revoked or expired link answers the same and stays as it was; the member or agent who joined with it stays (remove them from the members). A table link revokes at any time before it is full: the players already seated stay.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/invites": {"get": {"operationId": "list_my_invites", "summary": "List My Invites", "parameters": [], "responses": {"200": {"description": "Invites addressed to your verified email addresses.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/invites/{invite_id}/accept": {"post": {"operationId": "accept_invite", "summary": "Accept Invite", "parameters": [{"in": "path", "name": "invite_id", "schema": {"title": "Invite Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Joined: the new membership.", "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid", "description": "The membership."}, "world_id": {"type": "string", "format": "uuid"}, "world_name": {"type": "string"}, "role": {"type": "string"}, "joined_at": {"type": "string", "format": "date-time"}, "roster_character": {"type": ["string", "null"], "format": "uuid", "description": "The member's Character in the world, or null."}}, "required": ["id", "world_id", "world_name", "role", "joined_at", "roster_character"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Already a member (already_member). No account PIN is needed to accept; a write key for the membership waits for one (pin_required).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "requestBody": {"required": false, "description": "Optional: the Character in the world to join as.", "content": {"application/json": {"schema": {"type": "object", "properties": {"roster_character": {"type": "string", "format": "uuid", "description": "A Character in the world."}}}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/join": {"post": {"operationId": "accept_link", "summary": "Accept Link", "parameters": [], "responses": {"201": {"description": "Joined: the new membership (the same row as accepting an invite).", "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid", "description": "The membership."}, "world_id": {"type": "string", "format": "uuid"}, "world_name": {"type": "string"}, "role": {"type": "string"}, "joined_at": {"type": "string", "format": "date-time"}, "roster_character": {"type": ["string", "null"], "format": "uuid", "description": "The member's Character in the world, or null."}}, "required": ["id", "world_id", "world_name", "role", "joined_at", "roster_character"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "The code is not valid: unknown, used, revoked or expired, or made for an AI agent rather than a person (not_found). One answer for every case; the code is not spent.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Already a member (already_member). The code is not spent. No account PIN is needed to accept; a write key for the membership waits for one (pin_required).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A signed-in account accepts a person join link (the code after `#` in\nan invite link) and becomes a member with the link's role. Body\n`{\"code\": \"ow_j_...\"}`. A phone's `X-Session-Token` is credential enough.\nPreview first with POST /api/v2/join/preview (no credential).", "tags": ["Account"], "requestBody": {"required": true, "description": "The join code: the part of the invite link after #.", "content": {"application/json": {"schema": {"type": "object", "properties": {"code": {"type": "string", "description": "The ow_j_ join code."}}, "required": ["code"]}}}}, "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/invites/{invite_id}/refuse": {"post": {"operationId": "refuse_invite", "summary": "Refuse Invite", "parameters": [{"in": "path", "name": "invite_id", "schema": {"title": "Invite Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Invite refused.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/watched": {"get": {"operationId": "list_watched", "summary": "List Watched", "parameters": [], "responses": {"200": {"description": "Watched worlds.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Your watched worlds; a key that no longer works is marked stale.", "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "post": {"operationId": "add_watched", "summary": "Add Watched", "parameters": [], "responses": {"201": {"description": "Watching.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Body `{key}`: a live `ow_r_` read key, else 422.", "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/watched/{watch_id}": {"delete": {"operationId": "remove_watched", "summary": "Remove Watched", "parameters": [{"in": "path", "name": "watch_id", "schema": {"title": "Watch Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No longer watched.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/tokens": {"post": {"operationId": "mint_token", "summary": "Mint Token", "parameters": [], "responses": {"201": {"description": "Created: the token (shown once).", "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The label given at mint (a device, a tool)."}, "last4": {"type": "string", "description": "The token's last four characters, to tell tokens apart."}, "last_used_at": {"type": "string", "format": "date-time", "nullable": true}, "revoked": {"type": "boolean"}, "token": {"type": "string", "description": "The full `ow_a_` token, shown this once; keep it, it is never shown again."}}, "required": ["id", "name", "last4", "last_used_at", "revoked", "token"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Mint an ow_a_ account token. Full token shown ONCE.", "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}, "get": {"operationId": "list_tokens", "summary": "List Tokens", "parameters": [], "responses": {"200": {"description": "The account's tokens (last4 only).", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The label given at mint (a device, a tool)."}, "last4": {"type": "string", "description": "The token's last four characters, to tell tokens apart."}, "last_used_at": {"type": "string", "format": "date-time", "nullable": true}, "revoked": {"type": "boolean"}}, "required": ["id", "name", "last4", "last_used_at", "revoked"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "READ", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/account/tokens/{token_id}": {"delete": {"operationId": "revoke_token", "summary": "Revoke Token", "parameters": [{"in": "path", "name": "token_id", "schema": {"title": "Token Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "Revoked.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing or invalid account credential (Bearer ow_a_ token or session).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found, or not yours (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "Invalid request: unknown or read-only field, bad value, unknown query parameter, over-long text, extensions over the size cap (invalid_request); `param` names the culprit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Account"], "x-ow-scope": "WRITE", "security": [{"AccountToken": []}, {"SessionToken": []}]}}, "/api/v2/join": {"post": {"operationId": "join_world", "summary": "Join a world: an AI agent's seat, or a player's read key", "parameters": [], "responses": {"201": {"description": "Joined, shown once; two answers told apart by `kind`. An agent code (`kind` agent): your world, seat, Character, key and secret, and `members`: the world's roster with you in it (the rows of GET /members). `with_character` says whether this invitation gave you a Character: when false, `character` is null and `env` has no OW_CHARACTER line (you may create a Character later, like any member). A player code (`kind` player): a read key, the world, the Character you read as, and your role (guest); no secret and no PIN. The code is spent either way: a lost answer is a new link, not a replay. A table link is spent one place at a time: each 201 takes one, the last one spends it.", "content": {"application/json": {"schema": {"oneOf": [{"title": "AgentJoined", "type": "object", "properties": {"kind": {"const": "agent", "description": "An agent's seat: the answer below."}, "world": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "description": {"type": "string"}}, "required": ["id", "name", "description"]}, "seat": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "role": {"type": "string"}, "agent_name": {"type": "string"}}, "required": ["id", "role", "agent_name"]}, "with_character": {"type": "boolean"}, "character": {"type": ["object", "null"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}}, "required": ["id", "name"]}, "key": {"type": "string", "description": "Your write key (API-Key), shown once."}, "pin": {"type": "string", "description": "Your secret (API-Pin), shown once."}, "base_url": {"type": "string"}, "env": {"type": "string", "description": "The lines of a .env file."}, "next": {"type": "array", "items": {"type": "string"}}, "members": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": ["string", "null"], "format": "uuid", "description": "The membership id (what created_by holds), or null for the owner."}, "kind": {"type": "string", "enum": ["owner", "person", "agent", "player"]}, "role": {"type": "string", "enum": ["owner", "co_builder", "contributor", "guest"]}, "character": {"type": ["string", "null"], "format": "uuid", "description": "The Character that is this member (the owner's owner_character), or null."}, "status": {"type": "string", "enum": ["active", "removed"]}, "name": {"type": "string", "description": "Agent and player rows only: the agent's or the player's name."}}, "required": ["id", "kind", "role", "character", "status"]}}}, "required": ["kind", "world", "seat", "with_character", "character", "key", "pin", "base_url", "env", "next", "members"]}, {"title": "PlayerJoined", "type": "object", "properties": {"kind": {"const": "player", "description": "A player's seat: no account, read only, a guest."}, "key": {"type": "string", "description": "Your read key (API-Key, ow_r_), shown once. No PIN: a player reads only."}, "world": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "description": {"type": "string"}}, "required": ["id", "name", "description"]}, "character": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}}, "required": ["id", "name"], "description": "The Character you read as."}, "role": {"const": "guest"}}, "required": ["kind", "key", "world", "character", "role"], "additionalProperties": false}], "discriminator": {"propertyName": "kind"}}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "404": {"description": "The code is not valid: unknown, used, revoked or expired, or made for a person rather than an agent or a player (not_found). One answer for every case.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "For an agent code, agent_name is missing, blank or over 80 characters (param agent_name); for a player code, name is not text or over 80 characters (param name); or the body is not a JSON object. On a table link: the pick is taken (character_taken, param character), off the list or missing (invalid_request, param character), or the name is missing on a table with no list (param name). Any player code: the world has its 50 players (world_full). A refusal spends nothing.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "tags": ["Join"], "requestBody": {"required": true, "description": "The join code (the part of your link after #), and your name: `agent_name` for an agent code (required), `name` for a player code (optional; required on a table link with no list). A table link (a player code whose preview carries `characters`) also takes `character`, the player's pick.", "content": {"application/json": {"schema": {"type": "object", "properties": {"code": {"type": "string", "description": "The ow_j_ join code."}, "agent_name": {"type": "string", "minLength": 1, "maxLength": 80, "description": "Your name as an agent (not your human's name). Required for an agent code; a player code ignores it."}, "name": {"type": "string", "maxLength": 80, "description": "A player code only: the player's name, as the owner's members list shows it. Absent or blank: the name of the Character the link reads as, else Player. On a table link with no list (`name_required` in the preview) it is required: the player's fresh Character takes it."}, "character": {"type": "string", "format": "uuid", "description": "A table link only: the id of the Character the player picks, one of the preview's `characters` not `taken`. A Character a live seat already plays is 422 character_taken; one off the list (or hidden since the link was made) is 422 invalid_request, param character. Omitted on a table with no list; a link for one ignores it."}}, "required": ["code"]}}}}, "security": [], "x-ow-scope": "WRITE"}}, "/api/v2/join/preview": {"post": {"operationId": "preview_join", "summary": "Preview a join code without using it", "parameters": [], "responses": {"200": {"description": "The invitation the code holds, without using it: `reader` (`agent`: redeem it here at POST /join; `person`: a signed-in account accepts it at POST /account/join; `player`: redeem it at POST /join with no account for a read key), `world` {name, description}, `invited_by` (the name the inviter gave, unchecked, or null), `owner_character` (the name of the Character the world's owner chose as theirs, or null when none is set), `role` (always guest for a player), `with_character` (agents: true means redeeming gives you a Character in the world; false, you join without one; always false for a person, always true for a player), and `expires_at` (ISO 8601, UTC). A table link (a player code that works several times) adds `max_uses`, `uses_left`, `name_required` and `characters`, the owner's list in its order, each {id, name, image_url, taken}; a link for one answers without them. The code stays unused.", "content": {"application/json": {"schema": {"type": "object", "properties": {"reader": {"type": "string", "enum": ["agent", "person", "player"]}, "world": {"type": "object", "properties": {"name": {"type": "string"}, "description": {"type": "string"}}, "required": ["name", "description"]}, "invited_by": {"type": ["string", "null"]}, "owner_character": {"type": ["string", "null"]}, "role": {"type": "string"}, "with_character": {"type": "boolean"}, "expires_at": {"type": "string", "format": "date-time"}, "max_uses": {"type": "integer", "minimum": 2, "description": "A table link only: how many times the link works."}, "uses_left": {"type": "integer", "minimum": 0, "description": "A table link only: how many places are left."}, "name_required": {"type": "boolean", "description": "A table link only: true when the owner listed no Characters, so each player sends a `name` and gets a fresh Character with it."}, "characters": {"type": "array", "description": "A table link only: the Characters a player may pick, in the owner's order. `taken` is true while a live seat in the world plays it (a screen keeps the order and puts taken ones last). A Character hidden or deleted since the link was made is left out.", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "image_url": {"type": ["string", "null"]}, "taken": {"type": "boolean"}}, "required": ["id", "name", "image_url", "taken"]}}}, "required": ["reader", "world", "invited_by", "owner_character", "role", "with_character", "expires_at"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "404": {"description": "The code is not valid: unknown, used, revoked or expired (not_found). The same answer, byte for byte, as POST /join gives.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is missing or not a JSON object.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Who invites the agent, to which world, before it redeems. Writes\nnothing; every bad code answers exactly as the redeem does.", "tags": ["Join"], "requestBody": {"required": true, "description": "The join code (the part of your link after #).", "content": {"application/json": {"schema": {"type": "object", "properties": {"code": {"type": "string", "description": "The ow_j_ join code."}}, "required": ["code"]}}}}, "security": [], "x-ow-scope": "READ"}}, "/api/v2/members": {"get": {"operationId": "list_world_members", "summary": "Who is in this world (the roster)", "parameters": [], "responses": {"200": {"description": "The owner row first (id null), then active members by joined_at, then removed members by removed_at. Not paginated.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": ["string", "null"], "format": "uuid", "description": "The membership id (what created_by holds), or null for the owner."}, "kind": {"type": "string", "enum": ["owner", "person", "agent", "player"]}, "role": {"type": "string", "enum": ["owner", "co_builder", "contributor", "guest"]}, "character": {"type": ["string", "null"], "format": "uuid", "description": "The Character that is this member (the owner's owner_character), or null."}, "status": {"type": "string", "enum": ["active", "removed"]}, "name": {"type": "string", "description": "Agent and player rows only: the agent's or the player's name."}}, "required": ["id", "kind", "role", "character", "status"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key on the world. One row per membership, active and removed, so every element's created_by resolves to a row: a Narrative's `narrator` is what its writer claims, `created_by` is what the server recorded, and this joins the two. No usernames or emails: a Character is the public face. A Character the world's owner hid from this key is `character: null` on every row that plays it (the owner, the co-builders and the member who made it still see its id).", "tags": ["World"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/me": {"get": {"operationId": "get_me", "summary": "Who this key is (whoami)", "parameters": [], "responses": {"200": {"description": "The world, the key's scope, and who it acts as.", "content": {"application/json": {"schema": {"type": "object", "properties": {"world": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}}, "required": ["id", "name"]}, "scope": {"type": "string", "enum": ["read", "write"]}, "kind": {"type": "string", "enum": ["owner", "person", "agent", "player"]}, "role": {"type": "string", "enum": ["owner", "co_builder", "contributor", "guest"]}, "membership": {"type": ["string", "null"], "format": "uuid", "description": "Your membership id (what created_by holds for what you create), or null for the owner."}, "character": {"type": ["string", "null"], "format": "uuid"}, "name": {"type": "string", "description": "Agent and player seats only: your agent or player name."}}, "required": ["world", "scope", "kind", "role", "membership", "character"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key. An owner-minted or classic key answers as the owner (membership null, character = the world's owner_character); a member or agent-seat key answers as its membership. A read route: no PIN for `ow_` keys; a classic key on a private world sends its PIN. A PIN that is sent is checked: for a member key, against the member's own account PIN (an agent seat's secret), so a wrong one is a 401 here and not at the first write. A player seat (kind player, no account, read only) has no PIN: one it sends is ignored. `character` is null when the world's owner hid that Character from this key.", "tags": ["World"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/media/ticket": {"post": {"operationId": "create_media_ticket", "summary": "Get a ticket for one image upload", "parameters": [], "responses": {"201": {"description": "A ticket for one image upload. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to upload_url as is."}, "upload_url": {"type": "string", "description": "Where the image goes, with the ticket."}, "prefix": {"type": "string", "description": "u/<world_id>/: the object key prefix this world's uploads land under."}, "max_bytes": {"type": "integer", "description": "The most this ticket accepts: the per-image limit, or what the account has left, whichever is smaller."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused."}, "uses": {"type": "integer", "enum": [1], "description": "One ticket, one upload."}, "issued_to": {"type": "object", "description": "Who answers for the upload; it is recorded with the stored image. Owner and classic keys: the owner's account, membership null. A member key: the member's account and membership. An agent seat: its sponsor's account and the seat's id.", "properties": {"account": {"type": ["string", "null"], "format": "uuid"}, "membership": {"type": ["string", "null"], "format": "uuid"}}, "required": ["account", "membership"]}}, "required": ["ticket", "upload_url", "prefix", "max_bytes", "exp", "uses", "issued_to"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the account the upload counts against has used all of its image storage (code storage_full, type permission_error); the message names the account's cap.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts (rate_limited), or this world has reached its daily image upload limit (code quota_exceeded). Both carry a Retry-After header. Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "Image upload is not configured on this server (code media_unavailable, type api_error; no Retry-After). Or the server is busy: every request slot stayed full for 10 s (code server_busy, type api_error); retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN (owner, classic, member and agent-seat keys alike). No body. Returns a short-lived, single-use ticket that upload_url accepts for exactly one image upload into this world's prefix. The API never sees the image. Each ticket counts toward the world's daily limit, and each upload toward its account's image storage (1 GB by default).", "tags": ["Media"], "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/media/remove-ticket": {"post": {"operationId": "create_media_removal_ticket", "summary": "Get a ticket to remove one uploaded image", "parameters": [], "responses": {"200": {"description": "A ticket for removing one image. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"ticket": {"type": "string", "description": "Opaque to clients: send it to remove_url as Authorization: Bearer <ticket>. One ticket, one removal."}, "remove_url": {"type": "string", "description": "Where the removal goes, with the ticket: the upload host's POST /v1/remove (the same host as the upload ticket's upload_url)."}, "exp": {"type": "integer", "description": "Unix seconds after which the ticket is refused (10 minutes after it is issued)."}, "key": {"type": "string", "description": "The object key the ticket removes."}, "referenced": {"type": "integer", "description": "How many of this world's elements show the image (their image_url ends with the key). Removing the image does not change them: clear or replace their image_url yourself."}}, "required": ["ticket", "remove_url", "exp", "key", "referenced"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The credential is valid but lacks the scope: a read key on a write route (permission_error). Or the key belongs to neither the image's uploader nor the world's owner (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No image under this key that can be removed in this world (not_found): the key is outside u/<world_id>/, the image was not uploaded with a ticket, or it is already removed.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "A removal ticket for this image was issued less than 10 minutes ago and its removal is not yet done (code removal_in_flight, type invalid_request). Or no image is recorded under this key yet while an upload ticket of yours in this world is still open and unreported: the upload may have just finished (code upload_pending, type invalid_request; Retry-After 2). The platform cannot tell which key an open ticket will get, so any key answers upload_pending while you hold one, until it expires (10 minutes); then not_found. Both carry a Retry-After header.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}, "422": {"description": "The body is not a JSON object with a non-empty string `key` (invalid_request, param key).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "Image upload is not configured on this server (code media_unavailable, type api_error; no Retry-After). Or the server is busy: every request slot stayed full for 10 s (code server_busy, type api_error); retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "A write key and its PIN. The image's uploader may remove it, and so may the world's owner key; an agent seat answers as its sponsor. Only images uploaded with a ticket can be removed. Returns a short-lived, single-use ticket, the remove_url to send it to (the upload host's POST /v1/remove), its exp in unix seconds, and how many of this world's elements still show the image. Once the image is removed, its bytes stop counting toward the uploader's image storage.", "tags": ["Media"], "requestBody": {"required": true, "description": "The object key of the image to remove: the part of its image_url after https://media.onlyworlds.com/.", "content": {"application/json": {"schema": {"type": "object", "properties": {"key": {"type": "string", "minLength": 1, "maxLength": 512, "description": "u/<world_id>/<name>: an image uploaded into this world with a ticket."}}, "required": ["key"]}}}}, "x-ow-scope": "WRITE", "security": [{"ApiKey": [], "ApiPin": []}]}}, "/api/v2/world/files": {"get": {"operationId": "list_world_files", "summary": "List the world's files", "parameters": [], "responses": {"200": {"description": "The world's files and its total.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "description": "The file's name as uploaded."}, "kind": {"type": "string", "enum": ["pdf", "txt", "md", "json", "csv", "yaml", "toml", "zip"]}, "size": {"type": "integer", "description": "Bytes, as the upload host stored them."}, "label": {"type": ["string", "null"], "maxLength": 80, "description": "A field of the element's type it belongs to, free text, or null."}, "added_by": {"type": "object", "description": "Who added the file: a member's or a seat's name (the owner's username for the owner's keys), their membership id (null for the owner's keys), and whether an agent added it (a seat, the MCP surface, or a key that declared an agent).", "properties": {"name": {"type": "string"}, "membership": {"type": ["string", "null"], "format": "uuid"}, "agent": {"type": "boolean"}}, "required": ["name", "membership", "agent"]}, "added_at": {"type": "string", "format": "date-time"}, "element_type": {"type": "string"}, "element_id": {"type": "string", "format": "uuid"}}, "required": ["id", "name", "kind", "size", "label", "added_by", "added_at", "element_type", "element_id"]}}, "used_bytes": {"type": "integer", "description": "The world's live files' bytes."}, "cap_bytes": {"type": "integer", "description": "The world's room for files."}}, "required": ["data", "used_bytes", "cap_bytes"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "Any key. Every live file in the world that this key may see (each with its element's type and id), newest first, and the world's total against its cap. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["World"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/world/hidden": {"get": {"operationId": "list_world_hidden", "summary": "List the world's hidden elements", "parameters": [], "responses": {"200": {"description": "The hidden elements. Cache-Control: no-store.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"type": "object", "properties": {"type": {"type": "string", "description": "The element's type."}, "id": {"type": "string", "format": "uuid", "description": "The element's id."}, "set_at": {"type": "string", "format": "date-time"}, "set_by": {"type": ["string", "null"], "format": "uuid", "description": "The membership (person or seat) that hid it, or null when the world's owner did (as an element's created_by)."}}, "required": ["type", "id", "set_at", "set_by"]}}}, "required": ["data"]}}}, "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "Missing, invalid or revoked credential, or a wrong PIN (authentication_error; code key_revoked for a revoked key, invalid_credentials otherwise).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key is not the world's owner's or a co-builder's (permission_error).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many failed PIN attempts; see the Retry-After header (rate_limited). Or too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The world's owner key, or a co-builder's (read or write). Every hidden element's type and id, when it was hidden and by whom, newest first. A hidden element is seen only by the world's owner, its co-builders and the member who created it. To every other key it is as if it did not exist: a GET of it is 404, lists, search and counts leave it out, links to it are left out of the elements that carry them (those elements do not change), and a write naming its id answers as a missing id does. A key the owner minted (no member) reads as the owner and sees hidden elements. On /changes, a hide gives each contributor who loses the element (person or seat, never the member who made it) one delete row with reason `hidden`, at the change_seq the hide took; a guest gets no row, and its feed resets (409 resync_required) if it saw the element. An unhide resets every contributor and guest that regains it. A hidden Character is null on the roster (GET /members, GET /me, the world's owner_character) and can never be the Character a seat or a player plays (422 character_hidden). The hidden mark answers only where the server has it switched on; elsewhere every hidden route is a 404.", "tags": ["World"], "x-ow-scope": "READ", "security": [{"ApiKey": []}, {"ApiKey": [], "ApiPin": []}]}}, "/api/v2/files/uploaded": {"post": {"operationId": "report_file_uploaded", "summary": "Report a stored file (upload host)", "parameters": [], "responses": {"204": {"description": "Recorded.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "The X-OW-Signature header is missing or wrong (invalid_credentials).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "The ticket's upload was already reported with other values (code already_reported).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {tid, bytes, key} with the record's own key and a size from 1 to the ticket's max_bytes (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The upload host's report that a file's bytes are stored and checked: the record goes live with the reported size and counts toward the world's total. For the upload host only: no API-Key; the credential is the header X-OW-Signature, the lowercase hex HMAC-SHA256 of the exact body bytes under the ticket secret, as POST /media/uploaded. A missing or wrong signature is a 401; an unknown tid a 404; the same report again a 204. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Files"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"tid": {"type": "string", "format": "uuid", "description": "The ticket's id: the upload ticket's for /files/uploaded, the removal ticket's for /files/removed."}, "bytes": {"type": "integer", "description": "The stored object's size."}, "key": {"type": "string", "description": "w/<world>/<element>/<file_id>.<ext>"}}, "required": ["tid", "bytes", "key"]}}}}, "x-ow-scope": "WRITE", "security": []}}, "/api/v2/files/removed": {"post": {"operationId": "report_file_removed", "summary": "Report a removed file (upload host)", "parameters": [], "responses": {"204": {"description": "Recorded.", "headers": {"RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "401": {"description": "The X-OW-Signature header is missing or wrong (invalid_credentials).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "Not found in this world (not_found).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "The body is not {tid, bytes, key} with the record's own key and a size from 1 to the ticket's max_bytes (invalid_request; `param` names it).", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Too many requests: over the request budget (code too_many_requests, type rate_limited): 300 requests a minute per key or account token, 60 a minute per address for a request with neither. Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}, "RateLimit-Limit": {"$ref": "#/components/headers/RateLimit-Limit"}, "RateLimit-Remaining": {"$ref": "#/components/headers/RateLimit-Remaining"}, "RateLimit-Reset": {"$ref": "#/components/headers/RateLimit-Reset"}}}, "503": {"description": "The server is busy: every request slot stayed full for 10 s (code server_busy, type api_error). Retry after the Retry-After header's seconds.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}, "headers": {"Retry-After": {"description": "Seconds to wait before retrying.", "schema": {"type": "integer"}}}}}, "description": "The upload host's report that a removal ticket's object is deleted: the record is closed (it left every list and the world's total when keel signed the removal). For the upload host only: no API-Key; the credential is the header X-OW-Signature, the lowercase hex HMAC-SHA256 of the exact body bytes under the ticket secret, as POST /media/uploaded. A missing or wrong signature is a 401; an unknown tid a 404; the same report again a 204. Files on elements answer only where the server has them switched on; elsewhere every files route is a 404.", "tags": ["Files"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"tid": {"type": "string", "format": "uuid", "description": "The ticket's id: the upload ticket's for /files/uploaded, the removal ticket's for /files/removed."}, "bytes": {"type": "integer", "description": "The stored object's size."}, "key": {"type": "string", "description": "w/<world>/<element>/<file_id>.<ext>"}}, "required": ["tid", "bytes", "key"]}}}}, "x-ow-scope": "WRITE", "security": []}}}, "components": {"schemas": {"Error": {"title": "Error", "type": "object", "description": "The error envelope every v2 route answers with, an unknown path under /api/v2/ included (404 not_found). One exception: a method a path does not take (say DELETE /world) is a plain-text 405, not this envelope.\n\nEvery `code`, its HTTP status and `type`:\n\n| code | status | type | meaning |\n|---|---|---|---|\n| `invalid_request` | 422 | invalid_request | The body or query is malformed: an unknown field or query parameter, a bad value, or a wrong-shaped payload; `param` names the culprit. |\n| `invalid_link` | 400 | invalid_request | A link field names an element that does not exist in this world (or among a /bulk batch's surviving items). |\n| `id_conflict` | 409 | invalid_request | A create supplied an id that already exists, in this world or another (element ids are unique across all worlds). |\n| `resync_required` | 409 | invalid_request | A guest's, or (with the hidden mark on) a contributor's, /changes cursor predates a change to what it can see, or is not this caller's cursor shape: pull again from since=0. With the hidden mark on, the envelope's `reason` says why: `role` (your own role or membership moved: keep what the new walk does not name) or `view` (what you may see moved: drop it). |\n| `already_member` | 409 | invalid_request | The invite names someone who is already a member, or the accepting account already is one. |\n| `removal_in_flight` | 409 | invalid_request | A removal ticket for this image was issued less than 10 minutes ago and is not yet spent; use it, or retry after Retry-After. |\n| `upload_pending` | 409 | invalid_request | No image is recorded under this key yet, but an upload ticket in this world is still open and unreported (the upload may have just finished); retry after Retry-After (2 s). The platform cannot tell which key an open ticket will get, so any key answers this while the caller holds one, until it expires; then not_found. |\n| `pin_required` | 409 | invalid_request | A member without an account PIN tried to mint a write key. Accepting an invite or a link needs no PIN. |\n| `precondition_failed` | 412 | invalid_request | The request carried If-Match with a change_seq, and the element has moved since; the reply's top-level `current` is the element as it is now. Nothing was written and nothing is merged: re-read `current`, then re-send or ask the user. |\n| `player_role_fixed` | 422 | invalid_request | The owner tried to change a player seat's role: a player (no account) reads as a guest, and that role can't be changed. Remove the player instead. |\n| `character_hidden` | 422 | invalid_request | The owner tried to set a hidden Character as the one a seat or a player plays (`param` character): a played Character is on every reader's roster, so a hidden one can never be picked. Unhide it first, or pick another. |\n| `known_of_not_writable` | 422 | invalid_request | A write body carried `known_of` (`param` known_of): that key marks a stub, an element the reader knows of but does not see whole, and is never written. A stub sent back would blank every field it leaves out: drop `known_of` and send only the fields you mean to change. |\n| `character_taken` | 422 | invalid_request | A player picked a Character from a party link's list that a live seat in the world already plays (`param` character): pick another from the list. The preview marks each listed Character `taken`. |\n| `world_full` | 422 | invalid_request | A player link was opened in a world that already has its 50 live players: nothing was spent, and the link works again once the owner removes a player. |\n| `apply_failed` | 422 | invalid_request | A /bulk item passed validation but its write failed a database constraint (most often an id that already exists in another world). |\n| `file_too_large` | 413 | invalid_request | A file asked to be attached to an element is over 25 MB; the message gives both sizes (`param` size). |\n| `invalid_credentials` | 401 | authentication_error | The API key or PIN is missing, unrecognised or wrong. A PIN refusal names param API-Pin and says whether the PIN was missing or wrong. |\n| `key_revoked` | 401 | authentication_error | The API key was recognised but has been revoked. |\n| `permission_error` | 403 | permission_error | The credential is valid but lacks the scope for this route (e.g. a read key on a write route). |\n| `not_author` | 403 | permission_error | A contributor or guest key changed, replaced, relinked or deleted an element someone else created. |\n| `owner_only` | 403 | permission_error | A member key tried to change the world's own settings (a co-builder may change time_range_current alone). |\n| `view_as_owner_only` | 403 | permission_error | A member key sent `as` (view as): reading as another reader is the owner's, from a key with no member. |\n| `guest_not_supported` | 403 | permission_error | A guest key called a route guests cannot use yet, or wrote a note (Narrative) without a Character of its own in the world. |\n| `storage_full` | 403 | permission_error | The account the upload counts against has used all of its image storage. |\n| `files_full` | 403 | permission_error | The world's files have no room for this one (250 MB a world); the message gives the world's use, its cap and the file's size. Remove a file, or ask the owner. |\n| `not_found` | 404 | not_found | The element or route does not exist in this world. |\n| `gone` | 410 | not_found | The element was deleted from this world; the body carries the deletion's `change_seq` and `deleted_at` beside the envelope (GET of one element only). |\n| `rate_limited` | 429 | rate_limited | Too many failed PIN attempts; retry after the Retry-After header's seconds. |\n| `quota_exceeded` | 429 | rate_limited | A daily limit is used up (the world's image upload tickets, its file upload tickets, or the account's token rating for AI); retry after Retry-After. |\n| `too_many_requests` | 429 | rate_limited | Over the request budget: 300 requests a minute per key or account token, 60 a minute per address for a request with neither, reads and writes together (a /bulk call is one request); retry after Retry-After. Every reply carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. |\n| `idempotency_error` | 409 | idempotency_error | An Idempotency-Key was reused with a different request body. |\n| `idempotency_in_flight` | 409 | idempotency_error | A request with this Idempotency-Key is still running; retry after Retry-After (1 s): once it has answered, the same key and body replay its answer. |\n| `api_error` | 500 | api_error | An unexpected server-side error; the envelope is kept even here. |\n| `ask_failed` | 502 | api_error | The AI proxy could not get an answer from the model; try again in a moment. Nothing was counted. |\n| `ask_unavailable` | 503 | api_error | Asking your world is not switched on for this server, or is paused for the day (the server-wide cap); nothing was counted. |\n| `server_busy` | 503 | api_error | Every request slot stayed full for 10 seconds; retry after Retry-After. |\n| `payload_too_large` | 413 | api_error | The request body is over 2.5 MB; split a large /bulk into several calls. |\n| `media_unavailable` | 503 | api_error | Image or file upload is not configured on the server right now. |", "properties": {"error": {"type": "object", "properties": {"type": {"type": "string", "enum": ["api_error", "authentication_error", "idempotency_error", "invalid_request", "not_found", "permission_error", "rate_limited"]}, "code": {"type": "string", "enum": ["already_member", "api_error", "apply_failed", "ask_failed", "ask_unavailable", "character_hidden", "character_taken", "file_too_large", "files_full", "gone", "guest_not_supported", "id_conflict", "idempotency_error", "idempotency_in_flight", "invalid_credentials", "invalid_link", "invalid_request", "key_revoked", "known_of_not_writable", "media_unavailable", "not_author", "not_found", "owner_only", "payload_too_large", "permission_error", "pin_required", "player_role_fixed", "precondition_failed", "quota_exceeded", "rate_limited", "removal_in_flight", "resync_required", "server_busy", "storage_full", "too_many_requests", "upload_pending", "view_as_owner_only", "world_full"], "description": "Stable, machine-readable (e.g. invalid_link, id_conflict)."}, "message": {"type": "string"}, "param": {"type": ["string", "null"], "description": "The offending field or parameter, when there is one."}, "doc_url": {"type": "string"}, "reason": {"type": "string", "enum": ["role", "view"], "description": "Only on `resync_required`, where the server has the hidden mark on: why your view restarted. `role`: your own role or membership moved (a role change, a reactivation); keep what you hold that the new walk does not name. `view`: what you may see moved (a delete, a hide, an unhide, a Character given or taken); drop what the new walk does not name. Both since your cursor: `role`."}}, "required": ["type", "code", "message", "param", "doc_url"]}}, "required": ["error"], "x-ow-error-codes": {"invalid_request": {"type": "invalid_request", "status": 422, "summary": "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": {"type": "invalid_request", "status": 400, "summary": "A link field names an element that does not exist in this world (or among a /bulk batch's surviving items)."}, "id_conflict": {"type": "invalid_request", "status": 409, "summary": "A create supplied an id that already exists, in this world or another (element ids are unique across all worlds)."}, "resync_required": {"type": "invalid_request", "status": 409, "summary": "A guest's, or (with the hidden mark on) a contributor's, /changes cursor predates a change to what it can see, or is not this caller's cursor shape: pull again from since=0. With the hidden mark on, the envelope's `reason` says why: `role` (your own role or membership moved: keep what the new walk does not name) or `view` (what you may see moved: drop it)."}, "already_member": {"type": "invalid_request", "status": 409, "summary": "The invite names someone who is already a member, or the accepting account already is one."}, "removal_in_flight": {"type": "invalid_request", "status": 409, "summary": "A removal ticket for this image was issued less than 10 minutes ago and is not yet spent; use it, or retry after Retry-After."}, "upload_pending": {"type": "invalid_request", "status": 409, "summary": "No image is recorded under this key yet, but an upload ticket in this world is still open and unreported (the upload may have just finished); retry after Retry-After (2 s). The platform cannot tell which key an open ticket will get, so any key answers this while the caller holds one, until it expires; then not_found."}, "pin_required": {"type": "invalid_request", "status": 409, "summary": "A member without an account PIN tried to mint a write key. Accepting an invite or a link needs no PIN."}, "precondition_failed": {"type": "invalid_request", "status": 412, "summary": "The request carried If-Match with a change_seq, and the element has moved since; the reply's top-level `current` is the element as it is now. Nothing was written and nothing is merged: re-read `current`, then re-send or ask the user."}, "player_role_fixed": {"type": "invalid_request", "status": 422, "summary": "The owner tried to change a player seat's role: a player (no account) reads as a guest, and that role can't be changed. Remove the player instead."}, "character_hidden": {"type": "invalid_request", "status": 422, "summary": "The owner tried to set a hidden Character as the one a seat or a player plays (`param` character): a played Character is on every reader's roster, so a hidden one can never be picked. Unhide it first, or pick another."}, "known_of_not_writable": {"type": "invalid_request", "status": 422, "summary": "A write body carried `known_of` (`param` known_of): that key marks a stub, an element the reader knows of but does not see whole, and is never written. A stub sent back would blank every field it leaves out: drop `known_of` and send only the fields you mean to change."}, "character_taken": {"type": "invalid_request", "status": 422, "summary": "A player picked a Character from a party link's list that a live seat in the world already plays (`param` character): pick another from the list. The preview marks each listed Character `taken`."}, "world_full": {"type": "invalid_request", "status": 422, "summary": "A player link was opened in a world that already has its 50 live players: nothing was spent, and the link works again once the owner removes a player."}, "apply_failed": {"type": "invalid_request", "status": 422, "summary": "A /bulk item passed validation but its write failed a database constraint (most often an id that already exists in another world)."}, "file_too_large": {"type": "invalid_request", "status": 413, "summary": "A file asked to be attached to an element is over 25 MB; the message gives both sizes (`param` size)."}, "invalid_credentials": {"type": "authentication_error", "status": 401, "summary": "The API key or PIN is missing, unrecognised or wrong. A PIN refusal names param API-Pin and says whether the PIN was missing or wrong."}, "key_revoked": {"type": "authentication_error", "status": 401, "summary": "The API key was recognised but has been revoked."}, "permission_error": {"type": "permission_error", "status": 403, "summary": "The credential is valid but lacks the scope for this route (e.g. a read key on a write route)."}, "not_author": {"type": "permission_error", "status": 403, "summary": "A contributor or guest key changed, replaced, relinked or deleted an element someone else created."}, "owner_only": {"type": "permission_error", "status": 403, "summary": "A member key tried to change the world's own settings (a co-builder may change time_range_current alone)."}, "view_as_owner_only": {"type": "permission_error", "status": 403, "summary": "A member key sent `as` (view as): reading as another reader is the owner's, from a key with no member."}, "guest_not_supported": {"type": "permission_error", "status": 403, "summary": "A guest key called a route guests cannot use yet, or wrote a note (Narrative) without a Character of its own in the world."}, "storage_full": {"type": "permission_error", "status": 403, "summary": "The account the upload counts against has used all of its image storage."}, "files_full": {"type": "permission_error", "status": 403, "summary": "The world's files have no room for this one (250 MB a world); the message gives the world's use, its cap and the file's size. Remove a file, or ask the owner."}, "not_found": {"type": "not_found", "status": 404, "summary": "The element or route does not exist in this world."}, "gone": {"type": "not_found", "status": 410, "summary": "The element was deleted from this world; the body carries the deletion's `change_seq` and `deleted_at` beside the envelope (GET of one element only)."}, "rate_limited": {"type": "rate_limited", "status": 429, "summary": "Too many failed PIN attempts; retry after the Retry-After header's seconds."}, "quota_exceeded": {"type": "rate_limited", "status": 429, "summary": "A daily limit is used up (the world's image upload tickets, its file upload tickets, or the account's token rating for AI); retry after Retry-After."}, "too_many_requests": {"type": "rate_limited", "status": 429, "summary": "Over the request budget: 300 requests a minute per key or account token, 60 a minute per address for a request with neither, reads and writes together (a /bulk call is one request); retry after Retry-After. Every reply carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset."}, "idempotency_error": {"type": "idempotency_error", "status": 409, "summary": "An Idempotency-Key was reused with a different request body."}, "idempotency_in_flight": {"type": "idempotency_error", "status": 409, "summary": "A request with this Idempotency-Key is still running; retry after Retry-After (1 s): once it has answered, the same key and body replay its answer."}, "api_error": {"type": "api_error", "status": 500, "summary": "An unexpected server-side error; the envelope is kept even here."}, "ask_failed": {"type": "api_error", "status": 502, "summary": "The AI proxy could not get an answer from the model; try again in a moment. Nothing was counted."}, "ask_unavailable": {"type": "api_error", "status": 503, "summary": "Asking your world is not switched on for this server, or is paused for the day (the server-wide cap); nothing was counted."}, "server_busy": {"type": "api_error", "status": 503, "summary": "Every request slot stayed full for 10 seconds; retry after Retry-After."}, "payload_too_large": {"type": "api_error", "status": 413, "summary": "The request body is over 2.5 MB; split a large /bulk into several calls."}, "media_unavailable": {"type": "api_error", "status": 503, "summary": "Image or file upload is not configured on the server right now."}, "already_reported": {"type": "invalid_request", "status": 409, "internal": true, "summary": "The upload Worker reported a ticket's upload again with different values (POST /api/v2/media/uploaded, edge-only; the same values answer 204)."}, "ticket_required": {"type": null, "status": 401, "host": "upload.onlyworlds.com", "summary": "No ticket: send it as Authorization: Bearer <ticket>."}, "ticket_invalid": {"type": null, "status": 401, "host": "upload.onlyworlds.com", "summary": "The ticket is not valid: send it exactly as issued."}, "ticket_expired": {"type": null, "status": 401, "host": "upload.onlyworlds.com", "summary": "The ticket is past its expiry (issued for 10 minutes; the edge allows 60 s grace): get a new one."}, "ticket_used": {"type": null, "status": 401, "host": "upload.onlyworlds.com", "summary": "One ticket, one upload: get a new one for the next image."}, "bad_key": {"type": null, "status": 400, "host": "upload.onlyworlds.com", "summary": "The X-Key is not allowed (prefix, characters outside a-z 0-9 . _ - and single slashes, over 200 characters, or an extension that does not match the bytes); the response carries a `reason`."}, "empty_body": {"type": null, "status": 400, "host": "upload.onlyworlds.com", "summary": "No image bytes in the request."}, "method_not_allowed": {"type": null, "status": 405, "host": "upload.onlyworlds.com", "summary": "Only POST."}, "exists": {"type": null, "status": 409, "host": "upload.onlyworlds.com", "summary": "The key is taken and objects are never overwritten: for an image, choose another X-Key or omit it; for a file, the ticket's key is already stored (a retry after a success reads this as done)."}, "content_length_required": {"type": null, "status": 411, "host": "upload.onlyworlds.com", "summary": "Send a Content-Length header."}, "too_large": {"type": null, "status": 413, "host": "upload.onlyworlds.com", "summary": "The image is over the ticket's max_bytes."}, "unsupported_type": {"type": null, "status": 415, "host": "upload.onlyworlds.com", "summary": "An image must be webp, png, jpeg or avif; a file on an element must be a pdf, a text kind (txt, md, json, csv, yaml, toml) or a world's own zip bundle (the type is read from the bytes)."}, "write_failed": {"type": null, "status": 502, "host": "upload.onlyworlds.com", "summary": "Nothing was stored and the ticket is still unspent: retry with the same ticket."}, "not_removable": {"type": null, "status": 403, "host": "upload.onlyworlds.com", "summary": "The image is not one the removal ticket's world uploaded with a ticket, so it cannot be removed this way."}, "remove_failed": {"type": null, "status": 502, "host": "upload.onlyworlds.com", "summary": "Nothing was removed and the removal ticket is still unspent: retry with the same ticket."}, "ticket_lane_unconfigured": {"type": null, "status": 503, "host": "upload.onlyworlds.com", "summary": "Uploads (images and files alike) are not configured on the host right now: retry later."}}}, "Ability": {"title": "Ability", "type": "object", "description": "An ability as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "ability"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "activation": {"type": "string", "description": "Method or conditions under which the ability is activated."}, "challenges": {"type": "string", "description": "Describes specific difficulties or constraints that make the ability hard to master or use effectively."}, "prevalence": {"type": "string", "description": "How widely the ability is known or practiced, and potential clues to its origins and cultural diffusion."}, "duration": {"type": ["integer", "null"], "description": "Length of time the ability remains active or its effects persist, measured in TIME units."}, "potency": {"type": ["integer", "null"], "description": "Relative measure of the ability's inherent potency or force, used for scaling or comparison purposes."}, "range": {"type": ["integer", "null"], "description": "Effective reach or distance at which the ability can be used, in the world's distance unit (World distance_unit)."}, "tradition": {"type": ["string", "null"], "format": "uuid", "description": "A construct that expresses the conceptual, social, or institutional system this ability operates within. A Construct's id."}, "source": {"type": ["string", "null"], "format": "uuid", "description": "The phenomenon that serves as the enabling force or condition that allows this ability to function. A Phenomenon's id."}, "locus": {"type": ["string", "null"], "format": "uuid", "description": "Location where the ability is most strongly rooted, developed, or traditionally practiced. A Location's id."}, "effects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that result from the ability's use, such as environmental changes or sensory effects. Phenomenon ids."}, "talents": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits that naturally enhance or improve performance with this ability. Trait ids."}, "requisites": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Constructs that must be satisfied for the ability to be used, such as rituals, permissions, or required roles. Construct ids."}, "instruments": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects or tools required to activate, channel, or perform the ability. Object ids."}, "systems": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Magic frameworks or structures that the ability associates with. Construct ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for an ability); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "activation", "challenges", "prevalence", "duration", "potency", "range", "tradition", "source", "locus", "effects", "talents", "requisites", "instruments", "systems", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "AbilityWrite": {"title": "AbilityWrite", "type": "object", "description": "A full ability write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "activation": {"type": ["string", "null"], "description": "Method or conditions under which the ability is activated. null stores ''."}, "challenges": {"type": ["string", "null"], "description": "Describes specific difficulties or constraints that make the ability hard to master or use effectively. null stores ''."}, "prevalence": {"type": ["string", "null"], "description": "How widely the ability is known or practiced, and potential clues to its origins and cultural diffusion. null stores ''."}, "duration": {"type": ["integer", "null"], "description": "Length of time the ability remains active or its effects persist, measured in TIME units."}, "potency": {"type": ["integer", "null"], "description": "Relative measure of the ability's inherent potency or force, used for scaling or comparison purposes."}, "range": {"type": ["integer", "null"], "description": "Effective reach or distance at which the ability can be used, in the world's distance unit (World distance_unit)."}, "tradition": {"type": ["string", "null"], "format": "uuid", "description": "A construct that expresses the conceptual, social, or institutional system this ability operates within. Links a Construct: an existing one in this world."}, "source": {"type": ["string", "null"], "format": "uuid", "description": "The phenomenon that serves as the enabling force or condition that allows this ability to function. Links a Phenomenon: an existing one in this world."}, "locus": {"type": ["string", "null"], "format": "uuid", "description": "Location where the ability is most strongly rooted, developed, or traditionally practiced. Links a Location: an existing one in this world."}, "effects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that result from the ability's use, such as environmental changes or sensory effects. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "talents": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits that naturally enhance or improve performance with this ability. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "requisites": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Constructs that must be satisfied for the ability to be used, such as rituals, permissions, or required roles. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "instruments": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects or tools required to activate, channel, or perform the ability. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "systems": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Magic frameworks or structures that the ability associates with. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "AbilityPatch": {"title": "AbilityPatch", "type": "object", "description": "A partial ability write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "activation": {"type": ["string", "null"], "description": "Method or conditions under which the ability is activated. null stores ''."}, "challenges": {"type": ["string", "null"], "description": "Describes specific difficulties or constraints that make the ability hard to master or use effectively. null stores ''."}, "prevalence": {"type": ["string", "null"], "description": "How widely the ability is known or practiced, and potential clues to its origins and cultural diffusion. null stores ''."}, "duration": {"type": ["integer", "null"], "description": "Length of time the ability remains active or its effects persist, measured in TIME units."}, "potency": {"type": ["integer", "null"], "description": "Relative measure of the ability's inherent potency or force, used for scaling or comparison purposes."}, "range": {"type": ["integer", "null"], "description": "Effective reach or distance at which the ability can be used, in the world's distance unit (World distance_unit)."}, "tradition": {"type": ["string", "null"], "format": "uuid", "description": "A construct that expresses the conceptual, social, or institutional system this ability operates within. Links a Construct: an existing one in this world."}, "source": {"type": ["string", "null"], "format": "uuid", "description": "The phenomenon that serves as the enabling force or condition that allows this ability to function. Links a Phenomenon: an existing one in this world."}, "locus": {"type": ["string", "null"], "format": "uuid", "description": "Location where the ability is most strongly rooted, developed, or traditionally practiced. Links a Location: an existing one in this world."}, "effects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that result from the ability's use, such as environmental changes or sensory effects. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "talents": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits that naturally enhance or improve performance with this ability. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "requisites": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Constructs that must be satisfied for the ability to be used, such as rituals, permissions, or required roles. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "instruments": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects or tools required to activate, channel, or perform the ability. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "systems": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Magic frameworks or structures that the ability associates with. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "AbilityList": {"title": "AbilityList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Ability"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Collective": {"title": "Collective", "type": "object", "description": "A collective as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "collective"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "composition": {"type": "string", "description": "Internal structure or demographic makeup of the collective."}, "activity": {"type": "string", "description": "Primary behaviors or actions the collective engages in."}, "disposition": {"type": "string", "description": "Emotional control or volatility expressed by the collective."}, "state": {"type": "string", "description": "Current condition or operational status of the collective."}, "count": {"type": ["integer", "null"], "description": "Number of members in the collective (approximate or exact)."}, "formation_date": {"type": ["integer", "null"], "description": "Date the collective was formed, using world TIME units."}, "operator": {"type": ["string", "null"], "format": "uuid", "description": "Institution that manages or directs the collective. An Institution's id."}, "equipment": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Tools or gear in possession of and/or regularly used by the collective. Object ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities commonly shared among members of the collective, or abilities of that collective as a whole. Ability ids."}, "symbolism": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Cultural expressions, rituals, or symbols that unify or distinguish the collective. Construct ids."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species that compose or participate in the collective. Species ids."}, "characters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters who are members of the collective. Character ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures associated with or included in the collective. Creature ids."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that influence or characterize the collective. Phenomenon ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a collective); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "composition", "activity", "disposition", "state", "count", "formation_date", "operator", "equipment", "abilities", "symbolism", "species", "characters", "creatures", "phenomena", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "CollectiveWrite": {"title": "CollectiveWrite", "type": "object", "description": "A full collective write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "composition": {"type": ["string", "null"], "description": "Internal structure or demographic makeup of the collective. null stores ''."}, "activity": {"type": ["string", "null"], "description": "Primary behaviors or actions the collective engages in. null stores ''."}, "disposition": {"type": ["string", "null"], "description": "Emotional control or volatility expressed by the collective. null stores ''."}, "state": {"type": ["string", "null"], "description": "Current condition or operational status of the collective. null stores ''."}, "count": {"type": ["integer", "null"], "description": "Number of members in the collective (approximate or exact)."}, "formation_date": {"type": ["integer", "null"], "description": "Date the collective was formed, using world TIME units."}, "operator": {"type": ["string", "null"], "format": "uuid", "description": "Institution that manages or directs the collective. Links an Institution: an existing one in this world."}, "equipment": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Tools or gear in possession of and/or regularly used by the collective. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities commonly shared among members of the collective, or abilities of that collective as a whole. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "symbolism": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Cultural expressions, rituals, or symbols that unify or distinguish the collective. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species that compose or participate in the collective. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters who are members of the collective. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures associated with or included in the collective. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that influence or characterize the collective. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "CollectivePatch": {"title": "CollectivePatch", "type": "object", "description": "A partial collective write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "composition": {"type": ["string", "null"], "description": "Internal structure or demographic makeup of the collective. null stores ''."}, "activity": {"type": ["string", "null"], "description": "Primary behaviors or actions the collective engages in. null stores ''."}, "disposition": {"type": ["string", "null"], "description": "Emotional control or volatility expressed by the collective. null stores ''."}, "state": {"type": ["string", "null"], "description": "Current condition or operational status of the collective. null stores ''."}, "count": {"type": ["integer", "null"], "description": "Number of members in the collective (approximate or exact)."}, "formation_date": {"type": ["integer", "null"], "description": "Date the collective was formed, using world TIME units."}, "operator": {"type": ["string", "null"], "format": "uuid", "description": "Institution that manages or directs the collective. Links an Institution: an existing one in this world."}, "equipment": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Tools or gear in possession of and/or regularly used by the collective. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities commonly shared among members of the collective, or abilities of that collective as a whole. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "symbolism": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Cultural expressions, rituals, or symbols that unify or distinguish the collective. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species that compose or participate in the collective. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters who are members of the collective. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures associated with or included in the collective. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that influence or characterize the collective. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "CollectiveList": {"title": "CollectiveList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Collective"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Character": {"title": "Character", "type": "object", "description": "A character as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "character"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "physicality": {"type": "string", "description": "The character's visible physical features and body attributes."}, "mentality": {"type": "string", "description": "The character's mindset, emotional tone, and style of thinking."}, "background": {"type": "string", "description": "History, upbringing, or formative experiences of the character."}, "motivations": {"type": "string", "description": "Core desires, goals, or values that drive the character's choices and behavior."}, "reputation": {"type": "string", "description": "Brief summary of the character's current condition, role, or predicament."}, "height": {"type": ["integer", "null"], "description": "The character's approximate or exact height, in the world's length unit (World length_unit)."}, "weight": {"type": ["integer", "null"], "description": "The character's approximate or exact weight, in the world's mass unit (World mass_unit)."}, "birth_date": {"type": ["integer", "null"], "description": "Moment of birth, expressed in the world's TIME units."}, "charisma": {"type": ["integer", "null"], "description": "Ability to attract, inspire, and influence others."}, "coercion": {"type": ["integer", "null"], "description": "Capacity to dominate, intimidate, or apply force to shape outcomes."}, "competence": {"type": ["integer", "null"], "description": "Skill in planning, understanding, and managing complex systems or situations."}, "compassion": {"type": ["integer", "null"], "description": "Willingness to empathize with and care for others."}, "creativity": {"type": ["integer", "null"], "description": "Ability to generate novel ideas, perspectives, or solutions."}, "courage": {"type": ["integer", "null"], "description": "Readiness to face danger, risk, or adversity."}, "level": {"type": ["integer", "null"], "description": "Progression rank of the character in a game system."}, "hit_points": {"type": ["integer", "null"], "description": "Total health available to the character."}, "STR": {"type": ["integer", "null"], "description": "Physical force and carrying capacity."}, "DEX": {"type": ["integer", "null"], "description": "Agility, coordination, and reflexes."}, "CON": {"type": ["integer", "null"], "description": "Endurance and resistance to strain."}, "INT": {"type": ["integer", "null"], "description": "Reasoning, memory, and learning."}, "WIS": {"type": ["integer", "null"], "description": "Intuition, awareness, and judgment."}, "CHA": {"type": ["integer", "null"], "description": "Persuasiveness and personal magnetism."}, "birthplace": {"type": ["string", "null"], "format": "uuid", "description": "Location where the character was born. A Location's id."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "The character's present physical location. A Location's id."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species the character might belong to. Species ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits for notable behavioral, physical, or systemic characteristics. Trait ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities the character might perform, control, or invoke. Ability ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages the character can understand, speak, or use for communication. Language ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Key objects owned by or symbolically linked to the character. Object ids."}, "institutions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions the character is affiliated with. Institution ids."}, "family": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Families the character belongs to by blood or adoption. Family ids."}, "friends": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters the character considers close allies or companions. Character ids."}, "rivals": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters the character is in active opposition or competition with. Character ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (for a character: `physicality`, `height`, `weight`, `species`, `reputation`; a link among them still names only what the reader sees); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "physicality", "mentality", "background", "motivations", "reputation", "height", "weight", "birth_date", "charisma", "coercion", "competence", "compassion", "creativity", "courage", "level", "hit_points", "STR", "DEX", "CON", "INT", "WIS", "CHA", "birthplace", "location", "species", "traits", "abilities", "languages", "objects", "institutions", "family", "friends", "rivals", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "CharacterWrite": {"title": "CharacterWrite", "type": "object", "description": "A full character write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "physicality": {"type": ["string", "null"], "description": "The character's visible physical features and body attributes. null stores ''."}, "mentality": {"type": ["string", "null"], "description": "The character's mindset, emotional tone, and style of thinking. null stores ''."}, "background": {"type": ["string", "null"], "description": "History, upbringing, or formative experiences of the character. null stores ''."}, "motivations": {"type": ["string", "null"], "description": "Core desires, goals, or values that drive the character's choices and behavior. null stores ''."}, "reputation": {"type": ["string", "null"], "description": "Brief summary of the character's current condition, role, or predicament. null stores ''."}, "height": {"type": ["integer", "null"], "description": "The character's approximate or exact height, in the world's length unit (World length_unit)."}, "weight": {"type": ["integer", "null"], "description": "The character's approximate or exact weight, in the world's mass unit (World mass_unit)."}, "birth_date": {"type": ["integer", "null"], "description": "Moment of birth, expressed in the world's TIME units."}, "charisma": {"type": ["integer", "null"], "description": "Ability to attract, inspire, and influence others."}, "coercion": {"type": ["integer", "null"], "description": "Capacity to dominate, intimidate, or apply force to shape outcomes."}, "competence": {"type": ["integer", "null"], "description": "Skill in planning, understanding, and managing complex systems or situations."}, "compassion": {"type": ["integer", "null"], "description": "Willingness to empathize with and care for others."}, "creativity": {"type": ["integer", "null"], "description": "Ability to generate novel ideas, perspectives, or solutions."}, "courage": {"type": ["integer", "null"], "description": "Readiness to face danger, risk, or adversity."}, "level": {"type": ["integer", "null"], "description": "Progression rank of the character in a game system."}, "hit_points": {"type": ["integer", "null"], "description": "Total health available to the character."}, "STR": {"type": ["integer", "null"], "description": "Physical force and carrying capacity."}, "DEX": {"type": ["integer", "null"], "description": "Agility, coordination, and reflexes."}, "CON": {"type": ["integer", "null"], "description": "Endurance and resistance to strain."}, "INT": {"type": ["integer", "null"], "description": "Reasoning, memory, and learning."}, "WIS": {"type": ["integer", "null"], "description": "Intuition, awareness, and judgment."}, "CHA": {"type": ["integer", "null"], "description": "Persuasiveness and personal magnetism."}, "birthplace": {"type": ["string", "null"], "format": "uuid", "description": "Location where the character was born. Links a Location: an existing one in this world."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "The character's present physical location. Links a Location: an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species the character might belong to. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits for notable behavioral, physical, or systemic characteristics. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities the character might perform, control, or invoke. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages the character can understand, speak, or use for communication. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Key objects owned by or symbolically linked to the character. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions the character is affiliated with. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "family": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families the character belongs to by blood or adoption. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "friends": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters the character considers close allies or companions. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "rivals": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters the character is in active opposition or competition with. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "CharacterPatch": {"title": "CharacterPatch", "type": "object", "description": "A partial character write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "physicality": {"type": ["string", "null"], "description": "The character's visible physical features and body attributes. null stores ''."}, "mentality": {"type": ["string", "null"], "description": "The character's mindset, emotional tone, and style of thinking. null stores ''."}, "background": {"type": ["string", "null"], "description": "History, upbringing, or formative experiences of the character. null stores ''."}, "motivations": {"type": ["string", "null"], "description": "Core desires, goals, or values that drive the character's choices and behavior. null stores ''."}, "reputation": {"type": ["string", "null"], "description": "Brief summary of the character's current condition, role, or predicament. null stores ''."}, "height": {"type": ["integer", "null"], "description": "The character's approximate or exact height, in the world's length unit (World length_unit)."}, "weight": {"type": ["integer", "null"], "description": "The character's approximate or exact weight, in the world's mass unit (World mass_unit)."}, "birth_date": {"type": ["integer", "null"], "description": "Moment of birth, expressed in the world's TIME units."}, "charisma": {"type": ["integer", "null"], "description": "Ability to attract, inspire, and influence others."}, "coercion": {"type": ["integer", "null"], "description": "Capacity to dominate, intimidate, or apply force to shape outcomes."}, "competence": {"type": ["integer", "null"], "description": "Skill in planning, understanding, and managing complex systems or situations."}, "compassion": {"type": ["integer", "null"], "description": "Willingness to empathize with and care for others."}, "creativity": {"type": ["integer", "null"], "description": "Ability to generate novel ideas, perspectives, or solutions."}, "courage": {"type": ["integer", "null"], "description": "Readiness to face danger, risk, or adversity."}, "level": {"type": ["integer", "null"], "description": "Progression rank of the character in a game system."}, "hit_points": {"type": ["integer", "null"], "description": "Total health available to the character."}, "STR": {"type": ["integer", "null"], "description": "Physical force and carrying capacity."}, "DEX": {"type": ["integer", "null"], "description": "Agility, coordination, and reflexes."}, "CON": {"type": ["integer", "null"], "description": "Endurance and resistance to strain."}, "INT": {"type": ["integer", "null"], "description": "Reasoning, memory, and learning."}, "WIS": {"type": ["integer", "null"], "description": "Intuition, awareness, and judgment."}, "CHA": {"type": ["integer", "null"], "description": "Persuasiveness and personal magnetism."}, "birthplace": {"type": ["string", "null"], "format": "uuid", "description": "Location where the character was born. Links a Location: an existing one in this world."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "The character's present physical location. Links a Location: an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species the character might belong to. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits for notable behavioral, physical, or systemic characteristics. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities the character might perform, control, or invoke. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages the character can understand, speak, or use for communication. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Key objects owned by or symbolically linked to the character. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions the character is affiliated with. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "family": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families the character belongs to by blood or adoption. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "friends": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters the character considers close allies or companions. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "rivals": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters the character is in active opposition or competition with. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "CharacterList": {"title": "CharacterList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Character"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Construct": {"title": "Construct", "type": "object", "description": "A construct as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "construct"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "rationale": {"type": "string", "description": "The internal reasoning, structure, or justification of how the construct functions or makes sense within the world."}, "history": {"type": "string", "description": "The historical development or ideation of the construct, and its place in wider historical contexts."}, "status": {"type": "string", "description": "The present condition or operational status of the construct."}, "reach": {"type": "string", "description": "The geographic, cultural, or political extent of the construct's influence."}, "start_date": {"type": ["integer", "null"], "description": "The point in time when the construct began or was first established (uses world's TIME definition)."}, "end_date": {"type": ["integer", "null"], "description": "The point in time when the construct ceased to function or lost its meaning."}, "founder": {"type": ["string", "null"], "format": "uuid", "description": "Character who conceived or initiated the construct. A Character's id."}, "custodian": {"type": ["string", "null"], "format": "uuid", "description": "Institution maintaining, enforcing, or exploiting the construct. An Institution's id."}, "characters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the construct. Character ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the construct. Object ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the construct. Location ids."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the construct. Species ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the construct. Creature ids."}, "institutions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the construct. Institution ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the construct. Trait ids."}, "collectives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the construct. Collective ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the construct. Zone ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the construct. Ability ids."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the construct. Phenomenon ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the construct. Language ids."}, "families": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the construct. Family ids."}, "relations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Relations relevant to the construct. Relation ids."}, "titles": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the construct. Title ids."}, "constructs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Other constructs relevant to the construct. Construct ids."}, "events": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Events relevant to the construct. Event ids."}, "narratives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Narratives relevant to the construct. Narrative ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a construct); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "rationale", "history", "status", "reach", "start_date", "end_date", "founder", "custodian", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "ConstructWrite": {"title": "ConstructWrite", "type": "object", "description": "A full construct write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "rationale": {"type": ["string", "null"], "description": "The internal reasoning, structure, or justification of how the construct functions or makes sense within the world. null stores ''."}, "history": {"type": ["string", "null"], "description": "The historical development or ideation of the construct, and its place in wider historical contexts. null stores ''."}, "status": {"type": ["string", "null"], "description": "The present condition or operational status of the construct. null stores ''."}, "reach": {"type": ["string", "null"], "description": "The geographic, cultural, or political extent of the construct's influence. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "The point in time when the construct began or was first established (uses world's TIME definition)."}, "end_date": {"type": ["integer", "null"], "description": "The point in time when the construct ceased to function or lost its meaning."}, "founder": {"type": ["string", "null"], "format": "uuid", "description": "Character who conceived or initiated the construct. Links a Character: an existing one in this world."}, "custodian": {"type": ["string", "null"], "format": "uuid", "description": "Institution maintaining, enforcing, or exploiting the construct. Links an Institution: an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the construct. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the construct. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the construct. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the construct. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the construct. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the construct. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the construct. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the construct. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the construct. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the construct. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the construct. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the construct. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the construct. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "relations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Relations relevant to the construct. Links Relation elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the construct. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Other constructs relevant to the construct. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "events": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events relevant to the construct. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "narratives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Narratives relevant to the construct. Links Narrative elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "ConstructPatch": {"title": "ConstructPatch", "type": "object", "description": "A partial construct write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "rationale": {"type": ["string", "null"], "description": "The internal reasoning, structure, or justification of how the construct functions or makes sense within the world. null stores ''."}, "history": {"type": ["string", "null"], "description": "The historical development or ideation of the construct, and its place in wider historical contexts. null stores ''."}, "status": {"type": ["string", "null"], "description": "The present condition or operational status of the construct. null stores ''."}, "reach": {"type": ["string", "null"], "description": "The geographic, cultural, or political extent of the construct's influence. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "The point in time when the construct began or was first established (uses world's TIME definition)."}, "end_date": {"type": ["integer", "null"], "description": "The point in time when the construct ceased to function or lost its meaning."}, "founder": {"type": ["string", "null"], "format": "uuid", "description": "Character who conceived or initiated the construct. Links a Character: an existing one in this world."}, "custodian": {"type": ["string", "null"], "format": "uuid", "description": "Institution maintaining, enforcing, or exploiting the construct. Links an Institution: an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the construct. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the construct. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the construct. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the construct. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the construct. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the construct. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the construct. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the construct. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the construct. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the construct. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the construct. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the construct. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the construct. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "relations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Relations relevant to the construct. Links Relation elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the construct. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Other constructs relevant to the construct. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "events": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events relevant to the construct. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "narratives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Narratives relevant to the construct. Links Narrative elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "ConstructList": {"title": "ConstructList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Construct"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Creature": {"title": "Creature", "type": "object", "description": "A creature as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "creature"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "appearance": {"type": "string", "description": "Visual description of the creature."}, "habits": {"type": "string", "description": "Typical behaviors, instincts, or recurring actions the creature tends to display."}, "demeanor": {"type": "string", "description": "The emotional tone or attitude the creature conveys through posture, expression, or aggression."}, "status": {"type": "string", "description": "Current situation or classification of the creature."}, "weight": {"type": ["integer", "null"], "description": "Approximate or exact weight of the creature, in the world's mass unit (World mass_unit)."}, "height": {"type": ["integer", "null"], "description": "Approximate height of the creature, in the world's length unit (World length_unit)."}, "birth_date": {"type": ["integer", "null"], "description": "The time of the creature's birth, recorded in the world's defined TIME unit."}, "challenge_rating": {"type": ["integer", "null"], "description": "Difficulty or threat level of the creature in a gameplay context."}, "hit_points": {"type": ["integer", "null"], "description": "Total health or durability value in combat."}, "armor_class": {"type": ["integer", "null"], "description": "Defense rating against physical attacks or effects."}, "speed": {"type": ["integer", "null"], "description": "Typical movement speed, in the world's distance unit (World distance_unit) per round."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Specific location where the creature is currently found or most associated with. A Location's id."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Larger area or region commonly inhabited or currently claimed by the creature. A Zone's id."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species this creature belongs to. Species ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits that influence the creature's behavior, capabilities, or appearance. Trait ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Innate or learned abilities the creature can perform or activate. Ability ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages the creature can understand, speak, or otherwise use to communicate. Language ids."}, "actions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Combat or tactical abilities the creature can perform or use. Ability ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a creature); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "appearance", "habits", "demeanor", "status", "weight", "height", "birth_date", "challenge_rating", "hit_points", "armor_class", "speed", "location", "zone", "species", "traits", "abilities", "languages", "actions", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "CreatureWrite": {"title": "CreatureWrite", "type": "object", "description": "A full creature write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "appearance": {"type": ["string", "null"], "description": "Visual description of the creature. null stores ''."}, "habits": {"type": ["string", "null"], "description": "Typical behaviors, instincts, or recurring actions the creature tends to display. null stores ''."}, "demeanor": {"type": ["string", "null"], "description": "The emotional tone or attitude the creature conveys through posture, expression, or aggression. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current situation or classification of the creature. null stores ''."}, "weight": {"type": ["integer", "null"], "description": "Approximate or exact weight of the creature, in the world's mass unit (World mass_unit)."}, "height": {"type": ["integer", "null"], "description": "Approximate height of the creature, in the world's length unit (World length_unit)."}, "birth_date": {"type": ["integer", "null"], "description": "The time of the creature's birth, recorded in the world's defined TIME unit."}, "challenge_rating": {"type": ["integer", "null"], "description": "Difficulty or threat level of the creature in a gameplay context."}, "hit_points": {"type": ["integer", "null"], "description": "Total health or durability value in combat."}, "armor_class": {"type": ["integer", "null"], "description": "Defense rating against physical attacks or effects."}, "speed": {"type": ["integer", "null"], "description": "Typical movement speed, in the world's distance unit (World distance_unit) per round."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Specific location where the creature is currently found or most associated with. Links a Location: an existing one in this world."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Larger area or region commonly inhabited or currently claimed by the creature. Links a Zone: an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species this creature belongs to. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits that influence the creature's behavior, capabilities, or appearance. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Innate or learned abilities the creature can perform or activate. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages the creature can understand, speak, or otherwise use to communicate. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "actions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Combat or tactical abilities the creature can perform or use. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "CreaturePatch": {"title": "CreaturePatch", "type": "object", "description": "A partial creature write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "appearance": {"type": ["string", "null"], "description": "Visual description of the creature. null stores ''."}, "habits": {"type": ["string", "null"], "description": "Typical behaviors, instincts, or recurring actions the creature tends to display. null stores ''."}, "demeanor": {"type": ["string", "null"], "description": "The emotional tone or attitude the creature conveys through posture, expression, or aggression. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current situation or classification of the creature. null stores ''."}, "weight": {"type": ["integer", "null"], "description": "Approximate or exact weight of the creature, in the world's mass unit (World mass_unit)."}, "height": {"type": ["integer", "null"], "description": "Approximate height of the creature, in the world's length unit (World length_unit)."}, "birth_date": {"type": ["integer", "null"], "description": "The time of the creature's birth, recorded in the world's defined TIME unit."}, "challenge_rating": {"type": ["integer", "null"], "description": "Difficulty or threat level of the creature in a gameplay context."}, "hit_points": {"type": ["integer", "null"], "description": "Total health or durability value in combat."}, "armor_class": {"type": ["integer", "null"], "description": "Defense rating against physical attacks or effects."}, "speed": {"type": ["integer", "null"], "description": "Typical movement speed, in the world's distance unit (World distance_unit) per round."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Specific location where the creature is currently found or most associated with. Links a Location: an existing one in this world."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Larger area or region commonly inhabited or currently claimed by the creature. Links a Zone: an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species this creature belongs to. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits that influence the creature's behavior, capabilities, or appearance. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Innate or learned abilities the creature can perform or activate. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages the creature can understand, speak, or otherwise use to communicate. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "actions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Combat or tactical abilities the creature can perform or use. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "CreatureList": {"title": "CreatureList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Creature"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Event": {"title": "Event", "type": "object", "description": "An event as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "event"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "history": {"type": "string", "description": "Historical context and background of the event."}, "challenges": {"type": "string", "description": "Adversity or difficulties faced during the event."}, "consequences": {"type": "string", "description": "Outcomes and impacts resulting from the event."}, "start_date": {"type": ["integer", "null"], "description": "Date on which the event began."}, "end_date": {"type": ["integer", "null"], "description": "Date on which the event concluded."}, "triggers": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Events that precipitated this event. Event ids."}, "characters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters who know about the event: they were present, aware of it, or involved in it. Character ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the event. Object ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the event. Location ids."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the event. Species ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the event. Creature ids."}, "institutions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the event. Institution ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the event. Trait ids."}, "collectives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Groups or collectives relevant to the event. Collective ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the event. Zone ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the event. Ability ids."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Natural or supernatural phenomena relevant to the event. Phenomenon ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the event. Language ids."}, "families": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the event. Family ids."}, "relations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Interpersonal or political relations relevant to the event. Relation ids."}, "titles": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the event. Title ids."}, "constructs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Concepts, laws, or built entities relevant to the event. Construct ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for an event); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "history", "challenges", "consequences", "start_date", "end_date", "triggers", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "EventWrite": {"title": "EventWrite", "type": "object", "description": "A full event write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "history": {"type": ["string", "null"], "description": "Historical context and background of the event. null stores ''."}, "challenges": {"type": ["string", "null"], "description": "Adversity or difficulties faced during the event. null stores ''."}, "consequences": {"type": ["string", "null"], "description": "Outcomes and impacts resulting from the event. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date on which the event began."}, "end_date": {"type": ["integer", "null"], "description": "Date on which the event concluded."}, "triggers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events that precipitated this event. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters who know about the event: they were present, aware of it, or involved in it. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the event. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the event. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the event. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the event. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the event. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the event. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Groups or collectives relevant to the event. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the event. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the event. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Natural or supernatural phenomena relevant to the event. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the event. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the event. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "relations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Interpersonal or political relations relevant to the event. Links Relation elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the event. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Concepts, laws, or built entities relevant to the event. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "EventPatch": {"title": "EventPatch", "type": "object", "description": "A partial event write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "history": {"type": ["string", "null"], "description": "Historical context and background of the event. null stores ''."}, "challenges": {"type": ["string", "null"], "description": "Adversity or difficulties faced during the event. null stores ''."}, "consequences": {"type": ["string", "null"], "description": "Outcomes and impacts resulting from the event. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date on which the event began."}, "end_date": {"type": ["integer", "null"], "description": "Date on which the event concluded."}, "triggers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events that precipitated this event. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters who know about the event: they were present, aware of it, or involved in it. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the event. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the event. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the event. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the event. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the event. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the event. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Groups or collectives relevant to the event. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the event. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the event. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Natural or supernatural phenomena relevant to the event. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the event. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the event. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "relations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Interpersonal or political relations relevant to the event. Links Relation elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the event. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Concepts, laws, or built entities relevant to the event. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "EventList": {"title": "EventList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Event"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Family": {"title": "Family", "type": "object", "description": "A family as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "family"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "spirit": {"type": "string", "description": "The core values or shared ethos that the family embodies."}, "history": {"type": "string", "description": "Background or origin story of the family."}, "reputation": {"type": "string", "description": "Current social, political, or general standing of the family."}, "traditions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Cultural practices, symbols, or customs overseen by the family. Construct ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits possibly found among members of the family. Trait ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities or special qualities possibly present in the family. Ability ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages spoken by, or associated with the family. Language ids."}, "ancestors": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Notable forebears or historic characters in the family's lineage. Character ids."}, "estates": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Key locations owned, governed, or symbolically tied to the family. Location ids."}, "governs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions administered or managed by the family. Institution ids."}, "heirlooms": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Important objects or artifacts handed down by the family. Object ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures owned, bonded to, or representing the family. Creature ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a family); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "spirit", "history", "reputation", "traditions", "traits", "abilities", "languages", "ancestors", "estates", "governs", "heirlooms", "creatures", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "FamilyWrite": {"title": "FamilyWrite", "type": "object", "description": "A full family write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "spirit": {"type": ["string", "null"], "description": "The core values or shared ethos that the family embodies. null stores ''."}, "history": {"type": ["string", "null"], "description": "Background or origin story of the family. null stores ''."}, "reputation": {"type": ["string", "null"], "description": "Current social, political, or general standing of the family. null stores ''."}, "traditions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Cultural practices, symbols, or customs overseen by the family. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits possibly found among members of the family. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities or special qualities possibly present in the family. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages spoken by, or associated with the family. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "ancestors": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Notable forebears or historic characters in the family's lineage. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "estates": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Key locations owned, governed, or symbolically tied to the family. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "governs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions administered or managed by the family. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "heirlooms": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Important objects or artifacts handed down by the family. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures owned, bonded to, or representing the family. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "FamilyPatch": {"title": "FamilyPatch", "type": "object", "description": "A partial family write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "spirit": {"type": ["string", "null"], "description": "The core values or shared ethos that the family embodies. null stores ''."}, "history": {"type": ["string", "null"], "description": "Background or origin story of the family. null stores ''."}, "reputation": {"type": ["string", "null"], "description": "Current social, political, or general standing of the family. null stores ''."}, "traditions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Cultural practices, symbols, or customs overseen by the family. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits possibly found among members of the family. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities or special qualities possibly present in the family. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages spoken by, or associated with the family. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "ancestors": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Notable forebears or historic characters in the family's lineage. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "estates": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Key locations owned, governed, or symbolically tied to the family. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "governs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions administered or managed by the family. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "heirlooms": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Important objects or artifacts handed down by the family. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures owned, bonded to, or representing the family. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "FamilyList": {"title": "FamilyList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Family"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Institution": {"title": "Institution", "type": "object", "description": "An institution as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "institution"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "doctrine": {"type": "string", "description": "Core belief, mission, or purpose that drives the institution."}, "status": {"type": "string", "description": "Current political, cultural, or functional standing of the institution in the world."}, "founding_date": {"type": ["integer", "null"], "description": "Date when the institution was established, in the world's TIME format."}, "parent_institution": {"type": ["string", "null"], "format": "uuid", "description": "Institution that governs, embodies, or originated this one. An Institution's id."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Areas the institution controls or claims authority over. Zone ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Significant objects owned or tied to the institution's operations, holdings, or identity. Object ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures under the institution's protection, use, or symbolic control. Creature ids."}, "allies": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions this one actively cooperates or aligns with. Institution ids."}, "adversaries": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions this one opposes, competes with, or is in conflict with. Institution ids."}, "constructs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Conceptual, procedural, or structural systems created or maintained by the institution. Construct ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for an institution); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "doctrine", "status", "founding_date", "parent_institution", "zones", "objects", "creatures", "allies", "adversaries", "constructs", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "InstitutionWrite": {"title": "InstitutionWrite", "type": "object", "description": "A full institution write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "doctrine": {"type": ["string", "null"], "description": "Core belief, mission, or purpose that drives the institution. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current political, cultural, or functional standing of the institution in the world. null stores ''."}, "founding_date": {"type": ["integer", "null"], "description": "Date when the institution was established, in the world's TIME format."}, "parent_institution": {"type": ["string", "null"], "format": "uuid", "description": "Institution that governs, embodies, or originated this one. Links an Institution: an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Areas the institution controls or claims authority over. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Significant objects owned or tied to the institution's operations, holdings, or identity. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures under the institution's protection, use, or symbolic control. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "allies": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions this one actively cooperates or aligns with. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "adversaries": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions this one opposes, competes with, or is in conflict with. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Conceptual, procedural, or structural systems created or maintained by the institution. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "InstitutionPatch": {"title": "InstitutionPatch", "type": "object", "description": "A partial institution write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "doctrine": {"type": ["string", "null"], "description": "Core belief, mission, or purpose that drives the institution. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current political, cultural, or functional standing of the institution in the world. null stores ''."}, "founding_date": {"type": ["integer", "null"], "description": "Date when the institution was established, in the world's TIME format."}, "parent_institution": {"type": ["string", "null"], "format": "uuid", "description": "Institution that governs, embodies, or originated this one. Links an Institution: an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Areas the institution controls or claims authority over. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Significant objects owned or tied to the institution's operations, holdings, or identity. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures under the institution's protection, use, or symbolic control. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "allies": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions this one actively cooperates or aligns with. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "adversaries": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions this one opposes, competes with, or is in conflict with. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Conceptual, procedural, or structural systems created or maintained by the institution. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "InstitutionList": {"title": "InstitutionList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Institution"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Language": {"title": "Language", "type": "object", "description": "A language as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "language"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "phonology": {"type": "string", "description": "The language's sound systems, including phonemes, tone, and pronunciation rules."}, "grammar": {"type": "string", "description": "Rules governing syntax, morphology, and sentence structure."}, "lexicon": {"type": "string", "description": "Vocabulary principles or full word lists used in the language."}, "writing": {"type": "string", "description": "Script or notation system used to represent the language in written form."}, "status": {"type": "string", "description": "Current vitality, reputation, or dominance of the language."}, "classification": {"type": ["string", "null"], "format": "uuid", "description": "Linguistic group or typological category the language belongs to. A Construct's id."}, "spread": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Geographical areas where the language is used or spoken. Location ids."}, "dialects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Variants or dialect languages derived from the language. Language ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a language); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "phonology", "grammar", "lexicon", "writing", "status", "classification", "spread", "dialects", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "LanguageWrite": {"title": "LanguageWrite", "type": "object", "description": "A full language write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "phonology": {"type": ["string", "null"], "description": "The language's sound systems, including phonemes, tone, and pronunciation rules. null stores ''."}, "grammar": {"type": ["string", "null"], "description": "Rules governing syntax, morphology, and sentence structure. null stores ''."}, "lexicon": {"type": ["string", "null"], "description": "Vocabulary principles or full word lists used in the language. null stores ''."}, "writing": {"type": ["string", "null"], "description": "Script or notation system used to represent the language in written form. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current vitality, reputation, or dominance of the language. null stores ''."}, "classification": {"type": ["string", "null"], "format": "uuid", "description": "Linguistic group or typological category the language belongs to. Links a Construct: an existing one in this world."}, "spread": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Geographical areas where the language is used or spoken. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "dialects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Variants or dialect languages derived from the language. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "LanguagePatch": {"title": "LanguagePatch", "type": "object", "description": "A partial language write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "phonology": {"type": ["string", "null"], "description": "The language's sound systems, including phonemes, tone, and pronunciation rules. null stores ''."}, "grammar": {"type": ["string", "null"], "description": "Rules governing syntax, morphology, and sentence structure. null stores ''."}, "lexicon": {"type": ["string", "null"], "description": "Vocabulary principles or full word lists used in the language. null stores ''."}, "writing": {"type": ["string", "null"], "description": "Script or notation system used to represent the language in written form. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current vitality, reputation, or dominance of the language. null stores ''."}, "classification": {"type": ["string", "null"], "format": "uuid", "description": "Linguistic group or typological category the language belongs to. Links a Construct: an existing one in this world."}, "spread": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Geographical areas where the language is used or spoken. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "dialects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Variants or dialect languages derived from the language. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "LanguageList": {"title": "LanguageList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Language"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Law": {"title": "Law", "type": "object", "description": "A law as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "law"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "declaration": {"type": "string", "description": "The formal wording, expression, or decree of the law."}, "purpose": {"type": "string", "description": "The intent, motivation, or justification for the law's creation."}, "date": {"type": ["integer", "null"], "description": "Date the law was formally established, in world TIME units."}, "parent_law": {"type": ["string", "null"], "format": "uuid", "description": "A law that this law derives from, modifies, or enhances. A Law's id."}, "author": {"type": ["string", "null"], "format": "uuid", "description": "The institution that created or issued the law. An Institution's id."}, "penalties": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Consequences intended to be applied when the law is contravened. Construct ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations where the law is supported or enforced. Location ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones where the law is supported or enforced. Zone ids."}, "prohibitions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Things that the law explicitly or effectively forbids. Construct ids."}, "adjudicators": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles responsible for interpreting or ruling on the law's application and jurisdiction. Title ids."}, "enforcers": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles responsible for enforcing or imposing the law. Title ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a law); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "declaration", "purpose", "date", "parent_law", "author", "penalties", "locations", "zones", "prohibitions", "adjudicators", "enforcers", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "LawWrite": {"title": "LawWrite", "type": "object", "description": "A full law write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "declaration": {"type": ["string", "null"], "description": "The formal wording, expression, or decree of the law. null stores ''."}, "purpose": {"type": ["string", "null"], "description": "The intent, motivation, or justification for the law's creation. null stores ''."}, "date": {"type": ["integer", "null"], "description": "Date the law was formally established, in world TIME units."}, "parent_law": {"type": ["string", "null"], "format": "uuid", "description": "A law that this law derives from, modifies, or enhances. Links a Law: an existing one in this world."}, "author": {"type": ["string", "null"], "format": "uuid", "description": "The institution that created or issued the law. Links an Institution: an existing one in this world."}, "penalties": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Consequences intended to be applied when the law is contravened. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations where the law is supported or enforced. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones where the law is supported or enforced. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "prohibitions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Things that the law explicitly or effectively forbids. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "adjudicators": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles responsible for interpreting or ruling on the law's application and jurisdiction. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "enforcers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles responsible for enforcing or imposing the law. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "LawPatch": {"title": "LawPatch", "type": "object", "description": "A partial law write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "declaration": {"type": ["string", "null"], "description": "The formal wording, expression, or decree of the law. null stores ''."}, "purpose": {"type": ["string", "null"], "description": "The intent, motivation, or justification for the law's creation. null stores ''."}, "date": {"type": ["integer", "null"], "description": "Date the law was formally established, in world TIME units."}, "parent_law": {"type": ["string", "null"], "format": "uuid", "description": "A law that this law derives from, modifies, or enhances. Links a Law: an existing one in this world."}, "author": {"type": ["string", "null"], "format": "uuid", "description": "The institution that created or issued the law. Links an Institution: an existing one in this world."}, "penalties": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Consequences intended to be applied when the law is contravened. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations where the law is supported or enforced. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones where the law is supported or enforced. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "prohibitions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Things that the law explicitly or effectively forbids. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "adjudicators": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles responsible for interpreting or ruling on the law's application and jurisdiction. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "enforcers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles responsible for enforcing or imposing the law. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "LawList": {"title": "LawList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Law"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Location": {"title": "Location", "type": "object", "description": "A location as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "location"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "form": {"type": "string", "description": "Visual and environmental aspects of the location."}, "function": {"type": "string", "description": "Main use, role, or purpose of the location within the world."}, "political_climate": {"type": "string", "description": "Political structure, stability, and dynamics of the location."}, "customs": {"type": "string", "description": "Cultural practices, habits, or festivals."}, "infrastructure": {"type": "string", "description": "Roads, ports, and other physical systems that enable the movement of goods and people."}, "architecture": {"type": "string", "description": "Look, form, and materials used in the built environment and location design."}, "defensibility": {"type": "string", "description": "Qualities of natural, constructed, and implemented defenses at the location."}, "founding_date": {"type": ["integer", "null"], "description": "Date on which the location was founded, established, or designated."}, "elevation": {"type": ["integer", "null"], "description": "Height or elevation of the location relative to surrounding terrain, in the world's distance unit (World distance_unit)."}, "parent_location": {"type": ["string", "null"], "format": "uuid", "description": "Wider location that this location is part of. A Location's id."}, "primary_power": {"type": ["string", "null"], "format": "uuid", "description": "Institution that has the highest degree of political control over the location. An Institution's id."}, "governing_title": {"type": ["string", "null"], "format": "uuid", "description": "Governing figure assigned by the location's primary power. A Title's id."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Zone of interest that is associated with the location. A Zone's id."}, "rival": {"type": ["string", "null"], "format": "uuid", "description": "Location with an active, traditional, or historical rivalry with this one. A Location's id."}, "partner": {"type": ["string", "null"], "format": "uuid", "description": "Location with active, cooperative, or historical ties to this one. A Location's id."}, "populations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Distinct collective groups or communities residing within the location. Collective ids."}, "secondary_powers": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions with significant political control. Institution ids."}, "founders": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Individual(s) who founded or named the location. Character ids."}, "cults": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Significant religious constructs practiced or recognized at the location. Construct ids."}, "delicacies": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Organisms or other species locally consumed or celebrated as specialty foods. Species ids."}, "extraction_methods": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Techniques or strategies used to gather natural resources. Construct ids."}, "extraction_goods": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Products and materials that are gathered or obtained. Construct ids."}, "industry_methods": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Techniques or workflows used to refine or manufacture goods. Construct ids."}, "industry_goods": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Products and materials that are refined or manufactured. Construct ids."}, "extraction_markets": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations that receive extracted goods through trade, interchange, or seizure. Location ids."}, "industry_markets": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations that receive industrial goods through trade, interchange, or seizure. Location ids."}, "currencies": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Trade media recognized or circulated at the location. Construct ids."}, "buildings": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Notable structural objects at the location. Object ids."}, "building_methods": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Techniques or systems used to construct structures at the location. Construct ids."}, "fighters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Military units or forces responsible for defending the location. Construct ids."}, "defensive_objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects or installations for defending the location. Object ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a location); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "form", "function", "political_climate", "customs", "infrastructure", "architecture", "defensibility", "founding_date", "elevation", "parent_location", "primary_power", "governing_title", "zone", "rival", "partner", "populations", "secondary_powers", "founders", "cults", "delicacies", "extraction_methods", "extraction_goods", "industry_methods", "industry_goods", "extraction_markets", "industry_markets", "currencies", "buildings", "building_methods", "fighters", "defensive_objects", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "LocationWrite": {"title": "LocationWrite", "type": "object", "description": "A full location write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "form": {"type": ["string", "null"], "description": "Visual and environmental aspects of the location. null stores ''."}, "function": {"type": ["string", "null"], "description": "Main use, role, or purpose of the location within the world. null stores ''."}, "political_climate": {"type": ["string", "null"], "description": "Political structure, stability, and dynamics of the location. null stores ''."}, "customs": {"type": ["string", "null"], "description": "Cultural practices, habits, or festivals. null stores ''."}, "infrastructure": {"type": ["string", "null"], "description": "Roads, ports, and other physical systems that enable the movement of goods and people. null stores ''."}, "architecture": {"type": ["string", "null"], "description": "Look, form, and materials used in the built environment and location design. null stores ''."}, "defensibility": {"type": ["string", "null"], "description": "Qualities of natural, constructed, and implemented defenses at the location. null stores ''."}, "founding_date": {"type": ["integer", "null"], "description": "Date on which the location was founded, established, or designated."}, "elevation": {"type": ["integer", "null"], "description": "Height or elevation of the location relative to surrounding terrain, in the world's distance unit (World distance_unit)."}, "parent_location": {"type": ["string", "null"], "format": "uuid", "description": "Wider location that this location is part of. Links a Location: an existing one in this world."}, "primary_power": {"type": ["string", "null"], "format": "uuid", "description": "Institution that has the highest degree of political control over the location. Links an Institution: an existing one in this world."}, "governing_title": {"type": ["string", "null"], "format": "uuid", "description": "Governing figure assigned by the location's primary power. Links a Title: an existing one in this world."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Zone of interest that is associated with the location. Links a Zone: an existing one in this world."}, "rival": {"type": ["string", "null"], "format": "uuid", "description": "Location with an active, traditional, or historical rivalry with this one. Links a Location: an existing one in this world."}, "partner": {"type": ["string", "null"], "format": "uuid", "description": "Location with active, cooperative, or historical ties to this one. Links a Location: an existing one in this world."}, "populations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Distinct collective groups or communities residing within the location. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "secondary_powers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions with significant political control. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "founders": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Individual(s) who founded or named the location. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "cults": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Significant religious constructs practiced or recognized at the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "delicacies": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Organisms or other species locally consumed or celebrated as specialty foods. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "extraction_methods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Techniques or strategies used to gather natural resources. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "extraction_goods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Products and materials that are gathered or obtained. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "industry_methods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Techniques or workflows used to refine or manufacture goods. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "industry_goods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Products and materials that are refined or manufactured. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "extraction_markets": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations that receive extracted goods through trade, interchange, or seizure. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "industry_markets": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations that receive industrial goods through trade, interchange, or seizure. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "currencies": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Trade media recognized or circulated at the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "buildings": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Notable structural objects at the location. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "building_methods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Techniques or systems used to construct structures at the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "fighters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Military units or forces responsible for defending the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "defensive_objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects or installations for defending the location. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "LocationPatch": {"title": "LocationPatch", "type": "object", "description": "A partial location write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "form": {"type": ["string", "null"], "description": "Visual and environmental aspects of the location. null stores ''."}, "function": {"type": ["string", "null"], "description": "Main use, role, or purpose of the location within the world. null stores ''."}, "political_climate": {"type": ["string", "null"], "description": "Political structure, stability, and dynamics of the location. null stores ''."}, "customs": {"type": ["string", "null"], "description": "Cultural practices, habits, or festivals. null stores ''."}, "infrastructure": {"type": ["string", "null"], "description": "Roads, ports, and other physical systems that enable the movement of goods and people. null stores ''."}, "architecture": {"type": ["string", "null"], "description": "Look, form, and materials used in the built environment and location design. null stores ''."}, "defensibility": {"type": ["string", "null"], "description": "Qualities of natural, constructed, and implemented defenses at the location. null stores ''."}, "founding_date": {"type": ["integer", "null"], "description": "Date on which the location was founded, established, or designated."}, "elevation": {"type": ["integer", "null"], "description": "Height or elevation of the location relative to surrounding terrain, in the world's distance unit (World distance_unit)."}, "parent_location": {"type": ["string", "null"], "format": "uuid", "description": "Wider location that this location is part of. Links a Location: an existing one in this world."}, "primary_power": {"type": ["string", "null"], "format": "uuid", "description": "Institution that has the highest degree of political control over the location. Links an Institution: an existing one in this world."}, "governing_title": {"type": ["string", "null"], "format": "uuid", "description": "Governing figure assigned by the location's primary power. Links a Title: an existing one in this world."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Zone of interest that is associated with the location. Links a Zone: an existing one in this world."}, "rival": {"type": ["string", "null"], "format": "uuid", "description": "Location with an active, traditional, or historical rivalry with this one. Links a Location: an existing one in this world."}, "partner": {"type": ["string", "null"], "format": "uuid", "description": "Location with active, cooperative, or historical ties to this one. Links a Location: an existing one in this world."}, "populations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Distinct collective groups or communities residing within the location. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "secondary_powers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions with significant political control. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "founders": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Individual(s) who founded or named the location. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "cults": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Significant religious constructs practiced or recognized at the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "delicacies": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Organisms or other species locally consumed or celebrated as specialty foods. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "extraction_methods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Techniques or strategies used to gather natural resources. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "extraction_goods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Products and materials that are gathered or obtained. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "industry_methods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Techniques or workflows used to refine or manufacture goods. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "industry_goods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Products and materials that are refined or manufactured. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "extraction_markets": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations that receive extracted goods through trade, interchange, or seizure. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "industry_markets": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations that receive industrial goods through trade, interchange, or seizure. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "currencies": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Trade media recognized or circulated at the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "buildings": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Notable structural objects at the location. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "building_methods": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Techniques or systems used to construct structures at the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "fighters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Military units or forces responsible for defending the location. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "defensive_objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects or installations for defending the location. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "LocationList": {"title": "LocationList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Location"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Map": {"title": "Map", "type": "object", "description": "A map as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "map"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "background_color": {"type": "string", "description": "Color of the space around the map when zoomed out."}, "hierarchy": {"type": ["integer", "null"], "description": "To associate or differentiate between maps with a common parent."}, "width": {"type": ["integer", "null"], "description": "In pixels."}, "height": {"type": ["integer", "null"], "description": "In pixels."}, "depth": {"type": ["integer", "null"], "description": "In pixels."}, "parent_map": {"type": ["string", "null"], "format": "uuid", "description": "Map within which this map is contained. A Map's id."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Location element that this map represents. A Location's id."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a map); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "background_color", "hierarchy", "width", "height", "depth", "parent_map", "location", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "MapWrite": {"title": "MapWrite", "type": "object", "description": "A full map write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "background_color": {"type": ["string", "null"], "description": "Color of the space around the map when zoomed out. null stores ''."}, "hierarchy": {"type": ["integer", "null"], "description": "To associate or differentiate between maps with a common parent."}, "width": {"type": ["integer", "null"], "description": "In pixels."}, "height": {"type": ["integer", "null"], "description": "In pixels."}, "depth": {"type": ["integer", "null"], "description": "In pixels."}, "parent_map": {"type": ["string", "null"], "format": "uuid", "description": "Map within which this map is contained. Links a Map: an existing one in this world."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Location element that this map represents. Links a Location: an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "MapPatch": {"title": "MapPatch", "type": "object", "description": "A partial map write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "background_color": {"type": ["string", "null"], "description": "Color of the space around the map when zoomed out. null stores ''."}, "hierarchy": {"type": ["integer", "null"], "description": "To associate or differentiate between maps with a common parent."}, "width": {"type": ["integer", "null"], "description": "In pixels."}, "height": {"type": ["integer", "null"], "description": "In pixels."}, "depth": {"type": ["integer", "null"], "description": "In pixels."}, "parent_map": {"type": ["string", "null"], "format": "uuid", "description": "Map within which this map is contained. Links a Map: an existing one in this world."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Location element that this map represents. Links a Location: an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "MapList": {"title": "MapList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Map"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Marker": {"title": "Marker", "type": "object", "description": "A marker as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "marker"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "x": {"type": ["integer", "null"], "description": "x coordinate, from bottom left of the map."}, "y": {"type": ["integer", "null"], "description": "y coordinate, from bottom left of the map."}, "z": {"type": ["integer", "null"], "description": "z coordinate, in case of depth."}, "order": {"type": ["integer", "null"], "description": "Sequence position when markers define a polygon or line (0 = first point); without it, markers keep the order they were made in."}, "map": {"type": ["string", "null"], "format": "uuid", "description": "Map this marker is placed on. A Map's id."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Zone that is defined by this marker. A Zone's id."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a marker); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "x", "y", "z", "order", "map", "zone", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "MarkerWrite": {"title": "MarkerWrite", "type": "object", "description": "A full marker write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "x": {"type": ["integer", "null"], "description": "x coordinate, from bottom left of the map."}, "y": {"type": ["integer", "null"], "description": "y coordinate, from bottom left of the map."}, "z": {"type": ["integer", "null"], "description": "z coordinate, in case of depth."}, "order": {"type": ["integer", "null"], "description": "Sequence position when markers define a polygon or line (0 = first point); without it, markers keep the order they were made in."}, "map": {"type": ["string", "null"], "format": "uuid", "description": "Map this marker is placed on. Links a Map: an existing one in this world."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Zone that is defined by this marker. Links a Zone: an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "MarkerPatch": {"title": "MarkerPatch", "type": "object", "description": "A partial marker write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "x": {"type": ["integer", "null"], "description": "x coordinate, from bottom left of the map."}, "y": {"type": ["integer", "null"], "description": "y coordinate, from bottom left of the map."}, "z": {"type": ["integer", "null"], "description": "z coordinate, in case of depth."}, "order": {"type": ["integer", "null"], "description": "Sequence position when markers define a polygon or line (0 = first point); without it, markers keep the order they were made in."}, "map": {"type": ["string", "null"], "format": "uuid", "description": "Map this marker is placed on. Links a Map: an existing one in this world."}, "zone": {"type": ["string", "null"], "format": "uuid", "description": "Zone that is defined by this marker. Links a Zone: an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "MarkerList": {"title": "MarkerList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Marker"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Narrative": {"title": "Narrative", "type": "object", "description": "A narrative as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "narrative"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "story": {"type": "string", "description": "Content of the narrative, as told or remembered."}, "consequences": {"type": "string", "description": "Outcomes or legacy of the narrative."}, "start_date": {"type": ["integer", "null"], "description": "Date when the narrative begins, measured in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the narrative ends, measured in world TIME units."}, "order": {"type": ["integer", "null"], "description": "Position of this narrative within a parent narrative's sequence."}, "parent_narrative": {"type": ["string", "null"], "format": "uuid", "description": "Larger narrative that this narrative takes place in. A Narrative's id."}, "protagonist": {"type": ["string", "null"], "format": "uuid", "description": "Primary character of the narrative. A Character's id."}, "antagonist": {"type": ["string", "null"], "format": "uuid", "description": "Opposing character of the narrative. A Character's id."}, "narrator": {"type": ["string", "null"], "format": "uuid", "description": "Character credited with telling or recording the narrative. A Character's id."}, "conservator": {"type": ["string", "null"], "format": "uuid", "description": "Institution that preserves or curates the narrative. An Institution's id."}, "events": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Events relevant to the narrative. Event ids."}, "characters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the narrative. Character ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the narrative. Object ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the narrative. Location ids."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the narrative. Species ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the narrative. Creature ids."}, "institutions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the narrative. Institution ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the narrative. Trait ids."}, "collectives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Groups relevant to the narrative. Collective ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the narrative. Zone ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the narrative. Ability ids."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the narrative. Phenomenon ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the narrative. Language ids."}, "families": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the narrative. Family ids."}, "relations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Relationships relevant to the narrative. Relation ids."}, "titles": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the narrative. Title ids."}, "constructs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Constructs relevant to the narrative. Construct ids."}, "laws": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Laws relevant to the narrative. Law ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a narrative); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "story", "consequences", "start_date", "end_date", "order", "parent_narrative", "protagonist", "antagonist", "narrator", "conservator", "events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "NarrativeWrite": {"title": "NarrativeWrite", "type": "object", "description": "A full narrative write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "story": {"type": ["string", "null"], "description": "Content of the narrative, as told or remembered. null stores ''."}, "consequences": {"type": ["string", "null"], "description": "Outcomes or legacy of the narrative. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date when the narrative begins, measured in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the narrative ends, measured in world TIME units."}, "order": {"type": ["integer", "null"], "description": "Position of this narrative within a parent narrative's sequence."}, "parent_narrative": {"type": ["string", "null"], "format": "uuid", "description": "Larger narrative that this narrative takes place in. Links a Narrative: an existing one in this world."}, "protagonist": {"type": ["string", "null"], "format": "uuid", "description": "Primary character of the narrative. Links a Character: an existing one in this world."}, "antagonist": {"type": ["string", "null"], "format": "uuid", "description": "Opposing character of the narrative. Links a Character: an existing one in this world."}, "narrator": {"type": ["string", "null"], "format": "uuid", "description": "Character credited with telling or recording the narrative. Links a Character: an existing one in this world."}, "conservator": {"type": ["string", "null"], "format": "uuid", "description": "Institution that preserves or curates the narrative. Links an Institution: an existing one in this world."}, "events": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events relevant to the narrative. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the narrative. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the narrative. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the narrative. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the narrative. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the narrative. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the narrative. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the narrative. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Groups relevant to the narrative. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the narrative. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the narrative. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the narrative. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the narrative. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the narrative. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "relations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Relationships relevant to the narrative. Links Relation elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the narrative. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Constructs relevant to the narrative. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "laws": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Laws relevant to the narrative. Links Law elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "NarrativePatch": {"title": "NarrativePatch", "type": "object", "description": "A partial narrative write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "story": {"type": ["string", "null"], "description": "Content of the narrative, as told or remembered. null stores ''."}, "consequences": {"type": ["string", "null"], "description": "Outcomes or legacy of the narrative. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date when the narrative begins, measured in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the narrative ends, measured in world TIME units."}, "order": {"type": ["integer", "null"], "description": "Position of this narrative within a parent narrative's sequence."}, "parent_narrative": {"type": ["string", "null"], "format": "uuid", "description": "Larger narrative that this narrative takes place in. Links a Narrative: an existing one in this world."}, "protagonist": {"type": ["string", "null"], "format": "uuid", "description": "Primary character of the narrative. Links a Character: an existing one in this world."}, "antagonist": {"type": ["string", "null"], "format": "uuid", "description": "Opposing character of the narrative. Links a Character: an existing one in this world."}, "narrator": {"type": ["string", "null"], "format": "uuid", "description": "Character credited with telling or recording the narrative. Links a Character: an existing one in this world."}, "conservator": {"type": ["string", "null"], "format": "uuid", "description": "Institution that preserves or curates the narrative. Links an Institution: an existing one in this world."}, "events": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events relevant to the narrative. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the narrative. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the narrative. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the narrative. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the narrative. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the narrative. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the narrative. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the narrative. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Groups relevant to the narrative. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the narrative. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the narrative. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the narrative. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the narrative. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the narrative. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "relations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Relationships relevant to the narrative. Links Relation elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the narrative. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Constructs relevant to the narrative. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "laws": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Laws relevant to the narrative. Links Law elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "NarrativeList": {"title": "NarrativeList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Narrative"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Object": {"title": "Object", "type": "object", "description": "An object as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "object"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "aesthetics": {"type": "string", "description": "Appearance, design, or visual presentation of the object."}, "utility": {"type": "string", "description": "Intended purpose or primary use of the object."}, "origins": {"type": "string", "description": "Background or history of the object."}, "weight": {"type": ["integer", "null"], "description": "Approximate or exact mass of the object, in the world's mass unit (World mass_unit)."}, "amount": {"type": ["integer", "null"], "description": "The number of identical units in this object entry."}, "parent_object": {"type": ["string", "null"], "format": "uuid", "description": "Larger object that this one is part of or contained within. An Object's id."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Physical place where the object is currently located or stored. A Location's id."}, "language": {"type": ["string", "null"], "format": "uuid", "description": "Required to read, understand, or activate the object. A Language's id."}, "materials": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "The physical matter that constitutes the object. Construct ids."}, "technology": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Mechanisms relating to the object's design or operation. Construct ids."}, "effects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena potentially triggered or emitted on object use. Phenomenon ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities that the object grants or enables. Ability ids."}, "consumes": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "What might be used or depleted on object use. Construct ids."}, "affinities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits that resonate with or enhance the object's use, function, or effects. Trait ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for an object); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "aesthetics", "utility", "origins", "weight", "amount", "parent_object", "location", "language", "materials", "technology", "effects", "abilities", "consumes", "affinities", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "ObjectWrite": {"title": "ObjectWrite", "type": "object", "description": "A full object write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "aesthetics": {"type": ["string", "null"], "description": "Appearance, design, or visual presentation of the object. null stores ''."}, "utility": {"type": ["string", "null"], "description": "Intended purpose or primary use of the object. null stores ''."}, "origins": {"type": ["string", "null"], "description": "Background or history of the object. null stores ''."}, "weight": {"type": ["integer", "null"], "description": "Approximate or exact mass of the object, in the world's mass unit (World mass_unit)."}, "amount": {"type": ["integer", "null"], "description": "The number of identical units in this object entry."}, "parent_object": {"type": ["string", "null"], "format": "uuid", "description": "Larger object that this one is part of or contained within. Links an Object: an existing one in this world."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Physical place where the object is currently located or stored. Links a Location: an existing one in this world."}, "language": {"type": ["string", "null"], "format": "uuid", "description": "Required to read, understand, or activate the object. Links a Language: an existing one in this world."}, "materials": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "The physical matter that constitutes the object. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "technology": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Mechanisms relating to the object's design or operation. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "effects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena potentially triggered or emitted on object use. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities that the object grants or enables. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "consumes": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "What might be used or depleted on object use. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "affinities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits that resonate with or enhance the object's use, function, or effects. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "ObjectPatch": {"title": "ObjectPatch", "type": "object", "description": "A partial object write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "aesthetics": {"type": ["string", "null"], "description": "Appearance, design, or visual presentation of the object. null stores ''."}, "utility": {"type": ["string", "null"], "description": "Intended purpose or primary use of the object. null stores ''."}, "origins": {"type": ["string", "null"], "description": "Background or history of the object. null stores ''."}, "weight": {"type": ["integer", "null"], "description": "Approximate or exact mass of the object, in the world's mass unit (World mass_unit)."}, "amount": {"type": ["integer", "null"], "description": "The number of identical units in this object entry."}, "parent_object": {"type": ["string", "null"], "format": "uuid", "description": "Larger object that this one is part of or contained within. Links an Object: an existing one in this world."}, "location": {"type": ["string", "null"], "format": "uuid", "description": "Physical place where the object is currently located or stored. Links a Location: an existing one in this world."}, "language": {"type": ["string", "null"], "format": "uuid", "description": "Required to read, understand, or activate the object. Links a Language: an existing one in this world."}, "materials": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "The physical matter that constitutes the object. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "technology": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Mechanisms relating to the object's design or operation. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "effects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena potentially triggered or emitted on object use. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities that the object grants or enables. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "consumes": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "What might be used or depleted on object use. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "affinities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits that resonate with or enhance the object's use, function, or effects. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "ObjectList": {"title": "ObjectList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Object"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Phenomenon": {"title": "Phenomenon", "type": "object", "description": "A phenomenon as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "phenomenon"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "expression": {"type": "string", "description": "How the phenomenon manifests or takes shape in the world."}, "effects": {"type": "string", "description": "The primary outcomes or changes caused by the phenomenon."}, "mythology": {"type": "string", "description": "Cultural, religious, or narrative meaning associated with the phenomenon."}, "duration": {"type": ["integer", "null"], "description": "The amount of time the phenomenon lasts, measured in world TIME units."}, "system": {"type": ["string", "null"], "format": "uuid", "description": "Broader phenomenon that this one is part of or linked to. A Phenomenon's id."}, "catalysts": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects or materials that initiate or enhance the phenomenon. Object ids."}, "empowerments": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities that initiate or enhance the phenomenon, or are initiated or enhanced by it. Ability ids."}, "triggers": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Conceptual mechanisms or patterns that cause the phenomenon to activate. Construct ids."}, "wielders": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters capable of intentionally directing or controlling the phenomenon. Character ids."}, "environments": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations where the phenomenon occurs or is known to manifest. Location ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a phenomenon); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "expression", "effects", "mythology", "duration", "system", "catalysts", "empowerments", "triggers", "wielders", "environments", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "PhenomenonWrite": {"title": "PhenomenonWrite", "type": "object", "description": "A full phenomenon write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "expression": {"type": ["string", "null"], "description": "How the phenomenon manifests or takes shape in the world. null stores ''."}, "effects": {"type": ["string", "null"], "description": "The primary outcomes or changes caused by the phenomenon. null stores ''."}, "mythology": {"type": ["string", "null"], "description": "Cultural, religious, or narrative meaning associated with the phenomenon. null stores ''."}, "duration": {"type": ["integer", "null"], "description": "The amount of time the phenomenon lasts, measured in world TIME units."}, "system": {"type": ["string", "null"], "format": "uuid", "description": "Broader phenomenon that this one is part of or linked to. Links a Phenomenon: an existing one in this world."}, "catalysts": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects or materials that initiate or enhance the phenomenon. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "empowerments": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities that initiate or enhance the phenomenon, or are initiated or enhanced by it. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "triggers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Conceptual mechanisms or patterns that cause the phenomenon to activate. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "wielders": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters capable of intentionally directing or controlling the phenomenon. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "environments": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations where the phenomenon occurs or is known to manifest. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "PhenomenonPatch": {"title": "PhenomenonPatch", "type": "object", "description": "A partial phenomenon write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "expression": {"type": ["string", "null"], "description": "How the phenomenon manifests or takes shape in the world. null stores ''."}, "effects": {"type": ["string", "null"], "description": "The primary outcomes or changes caused by the phenomenon. null stores ''."}, "mythology": {"type": ["string", "null"], "description": "Cultural, religious, or narrative meaning associated with the phenomenon. null stores ''."}, "duration": {"type": ["integer", "null"], "description": "The amount of time the phenomenon lasts, measured in world TIME units."}, "system": {"type": ["string", "null"], "format": "uuid", "description": "Broader phenomenon that this one is part of or linked to. Links a Phenomenon: an existing one in this world."}, "catalysts": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects or materials that initiate or enhance the phenomenon. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "empowerments": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities that initiate or enhance the phenomenon, or are initiated or enhanced by it. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "triggers": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Conceptual mechanisms or patterns that cause the phenomenon to activate. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "wielders": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters capable of intentionally directing or controlling the phenomenon. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "environments": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations where the phenomenon occurs or is known to manifest. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "PhenomenonList": {"title": "PhenomenonList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Phenomenon"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Pin": {"title": "Pin", "type": "object", "description": "A pin as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "pin"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "x": {"type": ["integer", "null"], "description": "x coordinate, from bottom left of the map."}, "y": {"type": ["integer", "null"], "description": "y coordinate, from bottom left of the map."}, "z": {"type": ["integer", "null"], "description": "z coordinate, in case of depth (optional)."}, "map": {"type": ["string", "null"], "format": "uuid", "description": "Map that the pin is placed on. A Map's id."}, "element_type": {"type": "string", "enum": ["", "ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"], "description": "The element this pin places on the map, of any type. Generic link: the linked element's type ('' when unset)."}, "element_id": {"type": ["string", "null"], "format": "uuid", "description": "The element this pin places on the map, of any type. Generic link: the linked element's id."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a pin); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "x", "y", "z", "map", "element_type", "element_id", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "PinWrite": {"title": "PinWrite", "type": "object", "description": "A full pin write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "x": {"type": ["integer", "null"], "description": "x coordinate, from bottom left of the map."}, "y": {"type": ["integer", "null"], "description": "y coordinate, from bottom left of the map."}, "z": {"type": ["integer", "null"], "description": "z coordinate, in case of depth (optional)."}, "map": {"type": ["string", "null"], "format": "uuid", "description": "Map that the pin is placed on. Links a Map: an existing one in this world."}, "element_type": {"type": ["string", "null"], "enum": ["", "ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone", null], "description": "The element this pin places on the map, of any type. Set together with element_id, or neither."}, "element_id": {"type": ["string", "null"], "format": "uuid", "description": "The element this pin places on the map, of any type. Set together with element_type, or neither."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "PinPatch": {"title": "PinPatch", "type": "object", "description": "A partial pin write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "x": {"type": ["integer", "null"], "description": "x coordinate, from bottom left of the map."}, "y": {"type": ["integer", "null"], "description": "y coordinate, from bottom left of the map."}, "z": {"type": ["integer", "null"], "description": "z coordinate, in case of depth (optional)."}, "map": {"type": ["string", "null"], "format": "uuid", "description": "Map that the pin is placed on. Links a Map: an existing one in this world."}, "element_type": {"type": ["string", "null"], "enum": ["", "ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone", null], "description": "The element this pin places on the map, of any type. Set together with element_id, or neither."}, "element_id": {"type": ["string", "null"], "format": "uuid", "description": "The element this pin places on the map, of any type. Set together with element_type, or neither."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "PinList": {"title": "PinList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Pin"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Relation": {"title": "Relation", "type": "object", "description": "A relation as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "relation"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "background": {"type": "string", "description": "History and origin of the relation."}, "start_date": {"type": ["integer", "null"], "description": "Date when the relation began, defined in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the relation ended if any, defined in world TIME units."}, "intensity": {"type": ["integer", "null"], "description": "Significance of the relation, on a relative scale of 0 to 100."}, "actor": {"type": ["string", "null"], "format": "uuid", "description": "Primary character defining the relation. A Character's id."}, "events": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Events where the relation is involved or relevant. Event ids."}, "characters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the relation. Character ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the relation. Object ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the relation. Location ids."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the relation. Species ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the relation. Creature ids."}, "institutions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the relation. Institution ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the relation. Trait ids."}, "collectives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the relation. Collective ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the relation. Zone ids."}, "abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the relation. Ability ids."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the relation. Phenomenon ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the relation. Language ids."}, "families": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the relation. Family ids."}, "titles": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the relation. Title ids."}, "constructs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Concepts, contracts, or principles relevant to the relation. Construct ids."}, "narratives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Narratives relevant to the relation. Narrative ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a relation); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "background", "start_date", "end_date", "intensity", "actor", "events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "narratives", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "RelationWrite": {"title": "RelationWrite", "type": "object", "description": "A full relation write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "background": {"type": ["string", "null"], "description": "History and origin of the relation. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date when the relation began, defined in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the relation ended if any, defined in world TIME units."}, "intensity": {"type": ["integer", "null"], "description": "Significance of the relation, on a relative scale of 0 to 100."}, "actor": {"type": ["string", "null"], "format": "uuid", "description": "Primary character defining the relation. Links a Character: an existing one in this world."}, "events": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events where the relation is involved or relevant. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the relation. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the relation. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the relation. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the relation. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the relation. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the relation. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the relation. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the relation. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the relation. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the relation. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the relation. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the relation. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the relation. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the relation. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Concepts, contracts, or principles relevant to the relation. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "narratives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Narratives relevant to the relation. Links Narrative elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "RelationPatch": {"title": "RelationPatch", "type": "object", "description": "A partial relation write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "background": {"type": ["string", "null"], "description": "History and origin of the relation. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date when the relation began, defined in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the relation ended if any, defined in world TIME units."}, "intensity": {"type": ["integer", "null"], "description": "Significance of the relation, on a relative scale of 0 to 100."}, "actor": {"type": ["string", "null"], "format": "uuid", "description": "Primary character defining the relation. Links a Character: an existing one in this world."}, "events": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Events where the relation is involved or relevant. Links Event elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters relevant to the relation. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects relevant to the relation. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the relation. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the relation. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the relation. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the relation. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Traits relevant to the relation. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the relation. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the relation. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities relevant to the relation. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the relation. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the relation. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the relation. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles relevant to the relation. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Concepts, contracts, or principles relevant to the relation. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "narratives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Narratives relevant to the relation. Links Narrative elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "RelationList": {"title": "RelationList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Relation"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Species": {"title": "Species", "type": "object", "description": "A species as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "species"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "appearance": {"type": "string", "description": "Typical physical or form features of the species."}, "instincts": {"type": "string", "description": "Innate behavioral drives and survival tendencies."}, "sociality": {"type": "string", "description": "Typical patterns of social behavior."}, "temperament": {"type": "string", "description": "Overall behavioral disposition."}, "communication": {"type": "string", "description": "Typical methods and approaches of interaction."}, "role": {"type": "string", "description": "The species' ecological or cultural function in the world."}, "life_span": {"type": ["integer", "null"], "description": "Average or typical life expectancy of an individual, defined in world TIME units."}, "weight": {"type": ["integer", "null"], "description": "Average or typical adult weight, in the world's mass unit (World mass_unit)."}, "aggression": {"type": ["integer", "null"], "description": "General aggressiveness level, on relative scale of 0 to 100."}, "parent_species": {"type": ["string", "null"], "format": "uuid", "description": "Species that the species is considered a subspecies of. A Species's id."}, "nourishment": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Other species consumed as food sources. Species ids."}, "reproduction": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Reproductive method(s) of the species. Construct ids."}, "adaptations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Special physiological or evolutionary abilities. Ability ids."}, "traits": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Behavioral patterns associated with the species. Trait ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations associated with the species or its habitat. Location ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones associated with the species or its habitat. Zone ids."}, "affinities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena associated with the species or its behavior. Phenomenon ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a species); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "appearance", "instincts", "sociality", "temperament", "communication", "role", "life_span", "weight", "aggression", "parent_species", "nourishment", "reproduction", "adaptations", "traits", "locations", "zones", "affinities", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "SpeciesWrite": {"title": "SpeciesWrite", "type": "object", "description": "A full species write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "appearance": {"type": ["string", "null"], "description": "Typical physical or form features of the species. null stores ''."}, "instincts": {"type": ["string", "null"], "description": "Innate behavioral drives and survival tendencies. null stores ''."}, "sociality": {"type": ["string", "null"], "description": "Typical patterns of social behavior. null stores ''."}, "temperament": {"type": ["string", "null"], "description": "Overall behavioral disposition. null stores ''."}, "communication": {"type": ["string", "null"], "description": "Typical methods and approaches of interaction. null stores ''."}, "role": {"type": ["string", "null"], "description": "The species' ecological or cultural function in the world. null stores ''."}, "life_span": {"type": ["integer", "null"], "description": "Average or typical life expectancy of an individual, defined in world TIME units."}, "weight": {"type": ["integer", "null"], "description": "Average or typical adult weight, in the world's mass unit (World mass_unit)."}, "aggression": {"type": ["integer", "null"], "description": "General aggressiveness level, on relative scale of 0 to 100."}, "parent_species": {"type": ["string", "null"], "format": "uuid", "description": "Species that the species is considered a subspecies of. Links a Species: an existing one in this world."}, "nourishment": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Other species consumed as food sources. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "reproduction": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Reproductive method(s) of the species. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "adaptations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Special physiological or evolutionary abilities. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Behavioral patterns associated with the species. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations associated with the species or its habitat. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones associated with the species or its habitat. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "affinities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena associated with the species or its behavior. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "SpeciesPatch": {"title": "SpeciesPatch", "type": "object", "description": "A partial species write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "appearance": {"type": ["string", "null"], "description": "Typical physical or form features of the species. null stores ''."}, "instincts": {"type": ["string", "null"], "description": "Innate behavioral drives and survival tendencies. null stores ''."}, "sociality": {"type": ["string", "null"], "description": "Typical patterns of social behavior. null stores ''."}, "temperament": {"type": ["string", "null"], "description": "Overall behavioral disposition. null stores ''."}, "communication": {"type": ["string", "null"], "description": "Typical methods and approaches of interaction. null stores ''."}, "role": {"type": ["string", "null"], "description": "The species' ecological or cultural function in the world. null stores ''."}, "life_span": {"type": ["integer", "null"], "description": "Average or typical life expectancy of an individual, defined in world TIME units."}, "weight": {"type": ["integer", "null"], "description": "Average or typical adult weight, in the world's mass unit (World mass_unit)."}, "aggression": {"type": ["integer", "null"], "description": "General aggressiveness level, on relative scale of 0 to 100."}, "parent_species": {"type": ["string", "null"], "format": "uuid", "description": "Species that the species is considered a subspecies of. Links a Species: an existing one in this world."}, "nourishment": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Other species consumed as food sources. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "reproduction": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Reproductive method(s) of the species. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "adaptations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Special physiological or evolutionary abilities. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "traits": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Behavioral patterns associated with the species. Links Trait elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations associated with the species or its habitat. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones associated with the species or its habitat. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "affinities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena associated with the species or its behavior. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "SpeciesList": {"title": "SpeciesList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Species"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Title": {"title": "Title", "type": "object", "description": "A title as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "title"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "authority": {"type": "string", "description": "Rights or powers granted by the title."}, "eligibility": {"type": "string", "description": "Conditions or qualifications for receiving or holding the title."}, "status": {"type": "string", "description": "Current state or general condition of the title."}, "history": {"type": "string", "description": "Background information on the title's origin, evolution, or significance."}, "grant_date": {"type": ["integer", "null"], "description": "Date on which the title was granted, defined in world TIME units."}, "revoke_date": {"type": ["integer", "null"], "description": "Date on which the title ended or was revoked, defined in world TIME units."}, "issuer": {"type": ["string", "null"], "format": "uuid", "description": "Institution that formally created or granted the title. An Institution's id."}, "body": {"type": ["string", "null"], "format": "uuid", "description": "Institution in which the title functions or holds relevance. An Institution's id."}, "superior_title": {"type": ["string", "null"], "format": "uuid", "description": "Another title that has authority over this one. A Title's id."}, "holders": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters who currently hold or represent the title. Character ids."}, "symbols": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects that symbolize or authorize the title. Object ids."}, "characters": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Characters otherwise relevant to the title. Character ids."}, "institutions": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the title. Institution ids."}, "families": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the title. Family ids."}, "zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the title. Zone ids."}, "locations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the title. Location ids."}, "objects": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Objects otherwise relevant to the title. Object ids."}, "constructs": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Constructs relevant to the title. Construct ids."}, "laws": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Laws relevant to the title. Law ids."}, "collectives": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the title. Collective ids."}, "creatures": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the title. Creature ids."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the title. Phenomenon ids."}, "species": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the title. Species ids."}, "languages": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the title. Language ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a title); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "authority", "eligibility", "status", "history", "grant_date", "revoke_date", "issuer", "body", "superior_title", "holders", "symbols", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "TitleWrite": {"title": "TitleWrite", "type": "object", "description": "A full title write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "authority": {"type": ["string", "null"], "description": "Rights or powers granted by the title. null stores ''."}, "eligibility": {"type": ["string", "null"], "description": "Conditions or qualifications for receiving or holding the title. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current state or general condition of the title. null stores ''."}, "history": {"type": ["string", "null"], "description": "Background information on the title's origin, evolution, or significance. null stores ''."}, "grant_date": {"type": ["integer", "null"], "description": "Date on which the title was granted, defined in world TIME units."}, "revoke_date": {"type": ["integer", "null"], "description": "Date on which the title ended or was revoked, defined in world TIME units."}, "issuer": {"type": ["string", "null"], "format": "uuid", "description": "Institution that formally created or granted the title. Links an Institution: an existing one in this world."}, "body": {"type": ["string", "null"], "format": "uuid", "description": "Institution in which the title functions or holds relevance. Links an Institution: an existing one in this world."}, "superior_title": {"type": ["string", "null"], "format": "uuid", "description": "Another title that has authority over this one. Links a Title: an existing one in this world."}, "holders": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters who currently hold or represent the title. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "symbols": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects that symbolize or authorize the title. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters otherwise relevant to the title. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the title. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the title. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the title. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the title. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects otherwise relevant to the title. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Constructs relevant to the title. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "laws": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Laws relevant to the title. Links Law elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the title. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the title. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the title. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the title. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the title. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "TitlePatch": {"title": "TitlePatch", "type": "object", "description": "A partial title write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "authority": {"type": ["string", "null"], "description": "Rights or powers granted by the title. null stores ''."}, "eligibility": {"type": ["string", "null"], "description": "Conditions or qualifications for receiving or holding the title. null stores ''."}, "status": {"type": ["string", "null"], "description": "Current state or general condition of the title. null stores ''."}, "history": {"type": ["string", "null"], "description": "Background information on the title's origin, evolution, or significance. null stores ''."}, "grant_date": {"type": ["integer", "null"], "description": "Date on which the title was granted, defined in world TIME units."}, "revoke_date": {"type": ["integer", "null"], "description": "Date on which the title ended or was revoked, defined in world TIME units."}, "issuer": {"type": ["string", "null"], "format": "uuid", "description": "Institution that formally created or granted the title. Links an Institution: an existing one in this world."}, "body": {"type": ["string", "null"], "format": "uuid", "description": "Institution in which the title functions or holds relevance. Links an Institution: an existing one in this world."}, "superior_title": {"type": ["string", "null"], "format": "uuid", "description": "Another title that has authority over this one. Links a Title: an existing one in this world."}, "holders": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters who currently hold or represent the title. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "symbols": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects that symbolize or authorize the title. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "characters": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Characters otherwise relevant to the title. Links Character elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "institutions": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Institutions relevant to the title. Links Institution elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "families": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Families relevant to the title. Links Family elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Zones relevant to the title. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "locations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Locations relevant to the title. Links Location elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "objects": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Objects otherwise relevant to the title. Links Object elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "constructs": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Constructs relevant to the title. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "laws": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Laws relevant to the title. Links Law elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "collectives": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Collectives relevant to the title. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "creatures": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Creatures relevant to the title. Links Creature elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena relevant to the title. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "species": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Species relevant to the title. Links Species elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "languages": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Languages relevant to the title. Links Language elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "TitleList": {"title": "TitleList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Title"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Trait": {"title": "Trait", "type": "object", "description": "A trait as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "trait"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "social_effects": {"type": "string", "description": "Relating to social relationships, reputation, or interaction dynamics."}, "physical_effects": {"type": "string", "description": "Relating to physical changes, limitations, or enhancements."}, "functional_effects": {"type": "string", "description": "Relating to practical or learned performance or aptitude."}, "personality_effects": {"type": "string", "description": "Relating to temperament, mental state, or personality expression."}, "behaviour_effects": {"type": "string", "description": "Relating to visible aspects and patterns of behavior."}, "significance": {"type": "string", "description": "Describes the trait's societal, symbolic, or systemic presence."}, "charisma": {"type": ["integer", "null"], "description": "Affecting a character's charisma score."}, "coercion": {"type": ["integer", "null"], "description": "Affecting a character's coercion score."}, "competence": {"type": ["integer", "null"], "description": "Affecting a character's competence score."}, "compassion": {"type": ["integer", "null"], "description": "Affecting a character's compassion score."}, "creativity": {"type": ["integer", "null"], "description": "Affecting a character's creativity score."}, "courage": {"type": ["integer", "null"], "description": "Affecting a character's courage score."}, "anti_trait": {"type": ["string", "null"], "format": "uuid", "description": "Opposing trait that contradicts or nullifies the trait. A Trait's id."}, "empowered_abilities": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Abilities strengthened or enabled by the trait. Ability ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a trait); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "social_effects", "physical_effects", "functional_effects", "personality_effects", "behaviour_effects", "significance", "charisma", "coercion", "competence", "compassion", "creativity", "courage", "anti_trait", "empowered_abilities", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "TraitWrite": {"title": "TraitWrite", "type": "object", "description": "A full trait write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "social_effects": {"type": ["string", "null"], "description": "Relating to social relationships, reputation, or interaction dynamics. null stores ''."}, "physical_effects": {"type": ["string", "null"], "description": "Relating to physical changes, limitations, or enhancements. null stores ''."}, "functional_effects": {"type": ["string", "null"], "description": "Relating to practical or learned performance or aptitude. null stores ''."}, "personality_effects": {"type": ["string", "null"], "description": "Relating to temperament, mental state, or personality expression. null stores ''."}, "behaviour_effects": {"type": ["string", "null"], "description": "Relating to visible aspects and patterns of behavior. null stores ''."}, "significance": {"type": ["string", "null"], "description": "Describes the trait's societal, symbolic, or systemic presence. null stores ''."}, "charisma": {"type": ["integer", "null"], "description": "Affecting a character's charisma score."}, "coercion": {"type": ["integer", "null"], "description": "Affecting a character's coercion score."}, "competence": {"type": ["integer", "null"], "description": "Affecting a character's competence score."}, "compassion": {"type": ["integer", "null"], "description": "Affecting a character's compassion score."}, "creativity": {"type": ["integer", "null"], "description": "Affecting a character's creativity score."}, "courage": {"type": ["integer", "null"], "description": "Affecting a character's courage score."}, "anti_trait": {"type": ["string", "null"], "format": "uuid", "description": "Opposing trait that contradicts or nullifies the trait. Links a Trait: an existing one in this world."}, "empowered_abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities strengthened or enabled by the trait. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "TraitPatch": {"title": "TraitPatch", "type": "object", "description": "A partial trait write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "social_effects": {"type": ["string", "null"], "description": "Relating to social relationships, reputation, or interaction dynamics. null stores ''."}, "physical_effects": {"type": ["string", "null"], "description": "Relating to physical changes, limitations, or enhancements. null stores ''."}, "functional_effects": {"type": ["string", "null"], "description": "Relating to practical or learned performance or aptitude. null stores ''."}, "personality_effects": {"type": ["string", "null"], "description": "Relating to temperament, mental state, or personality expression. null stores ''."}, "behaviour_effects": {"type": ["string", "null"], "description": "Relating to visible aspects and patterns of behavior. null stores ''."}, "significance": {"type": ["string", "null"], "description": "Describes the trait's societal, symbolic, or systemic presence. null stores ''."}, "charisma": {"type": ["integer", "null"], "description": "Affecting a character's charisma score."}, "coercion": {"type": ["integer", "null"], "description": "Affecting a character's coercion score."}, "competence": {"type": ["integer", "null"], "description": "Affecting a character's competence score."}, "compassion": {"type": ["integer", "null"], "description": "Affecting a character's compassion score."}, "creativity": {"type": ["integer", "null"], "description": "Affecting a character's creativity score."}, "courage": {"type": ["integer", "null"], "description": "Affecting a character's courage score."}, "anti_trait": {"type": ["string", "null"], "format": "uuid", "description": "Opposing trait that contradicts or nullifies the trait. Links a Trait: an existing one in this world."}, "empowered_abilities": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Abilities strengthened or enabled by the trait. Links Ability elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "TraitList": {"title": "TraitList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Trait"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "Zone": {"title": "Zone", "type": "object", "description": "A zone as the API returns it. Single links are an id or null, multi links an array of ids; extension fields appear inline. `?fields=` returns only the named keys, and `?expand=` replaces the named links' ids with ElementStub objects.", "properties": {"type": {"type": "string", "const": "zone"}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "maxLength": 255, "description": "Name of the element."}, "description": {"type": "string", "description": "Any kind of details about the element."}, "supertype": {"type": "string", "maxLength": 128, "description": "The top level category to which the element belongs."}, "subtype": {"type": "string", "maxLength": 128, "description": "The sub level category through which the element is further classified."}, "image_url": {"type": "string", "maxLength": 1024, "description": "URL to an image representing the element."}, "role": {"type": "string", "description": "The operational function or intent of the zone."}, "context": {"type": "string", "description": "Historical and key knowledge about the zone."}, "start_date": {"type": ["integer", "null"], "description": "Date when the zone becomes extant or relevant, defined in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the zone ceases to be meaningful or enforced, defined in world TIME units."}, "phenomena": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that affect, define, or occur within the zone. Phenomenon ids."}, "linked_zones": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Other zones that are associated with the zone. Zone ids."}, "populations": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Distinct collective groups or communities residing within the zone. Collective ids."}, "titles": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Titles assigned to represent, manage, or protect the zone. Title ids."}, "principles": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "Influential mechanics acted within, upon, or by the zone. Construct ids."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "change_seq": {"type": "integer", "description": "The world change sequence at this element's last write."}, "created_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that created this element, or null when the world's owner did."}, "changed_by": {"type": ["string", "null"], "format": "uuid", "readOnly": true, "description": "The world membership that last changed this element, or null when the world's owner (or the server) did, or no write has touched it since doors were recorded."}, "changed_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "readOnly": true, "description": "The door of the last change: `seat` (an agent seat), `mcp` (the MCP server), `declared` (the request sent an X-OW-Agent header), `api`, `v1`, `sync` (the mobile app's sync) or `server` (the server, on its own behalf). The first three mean an agent made it. Null: no write since doors were recorded."}, "known_of": {"type": "boolean", "const": true, "readOnly": true, "description": "Present, and true, only on an element this reader knows of but does not see whole: a guest or player reaches it only because its own Character's sheet links to it (one step out). Such a row keeps `id`, `type`, `name`, `description`, `supertype`, `subtype`, `image_url`, `created_at`, `updated_at` and `change_seq`, and its type's public fields (none for a zone); every other field reads as unset (text \"\", numbers and single links null, multi links [], `created_by`, `changed_by` and `changed_via` null, no extension fields), its files are 404, and search matches only what it keeps. Absent: the element is whole. Any other grant (it names the reader's Character, the reader knows it, it is on the roster, the reader made it) makes it whole; a hidden element is never even a stub, whatever its public fields. Read-only: a write carrying `known_of` is refused (`known_of_not_writable`)."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["type", "id", "name", "description", "supertype", "subtype", "image_url", "role", "context", "start_date", "end_date", "phenomena", "linked_zones", "populations", "titles", "principles", "created_at", "updated_at", "change_seq", "created_by", "changed_by", "changed_via"]}, "ZoneWrite": {"title": "ZoneWrite", "type": "object", "description": "A full zone write (POST creates; PUT creates or fully replaces, resetting omitted fields to empty). Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "role": {"type": ["string", "null"], "description": "The operational function or intent of the zone. null stores ''."}, "context": {"type": ["string", "null"], "description": "Historical and key knowledge about the zone. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date when the zone becomes extant or relevant, defined in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the zone ceases to be meaningful or enforced, defined in world TIME units."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that affect, define, or occur within the zone. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "linked_zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Other zones that are associated with the zone. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "populations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Distinct collective groups or communities residing within the zone. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles assigned to represent, manage, or protect the zone. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "principles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Influential mechanics acted within, upon, or by the zone. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false, "required": ["name"]}, "ZonePatch": {"title": "ZonePatch", "type": "object", "description": "A partial zone write (PATCH): omitted fields are untouched; arrays replace; extension fields merge by key. Any key that is neither a field below nor an extension field (atlas_*, shadow_*, x_*) is a 422, including the server-managed type, created_at, updated_at and change_seq.", "properties": {"id": {"type": "string", "format": "uuid", "description": "POST: optional client-minted id (any RFC 4122 UUID; the server mints a UUIDv7 otherwise). PUT/PATCH: ignored, the path id wins."}, "name": {"type": ["string", "null"], "description": "Name of the element. null stores ''.", "maxLength": 255}, "description": {"type": ["string", "null"], "description": "Any kind of details about the element. null stores ''."}, "supertype": {"type": ["string", "null"], "description": "The top level category to which the element belongs. null stores ''.", "maxLength": 128}, "subtype": {"type": ["string", "null"], "description": "The sub level category through which the element is further classified. null stores ''.", "maxLength": 128}, "image_url": {"type": ["string", "null"], "description": "URL to an image representing the element. null stores ''.", "maxLength": 1024}, "role": {"type": ["string", "null"], "description": "The operational function or intent of the zone. null stores ''."}, "context": {"type": ["string", "null"], "description": "Historical and key knowledge about the zone. null stores ''."}, "start_date": {"type": ["integer", "null"], "description": "Date when the zone becomes extant or relevant, defined in world TIME units."}, "end_date": {"type": ["integer", "null"], "description": "Date when the zone ceases to be meaningful or enforced, defined in world TIME units."}, "phenomena": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Phenomena that affect, define, or occur within the zone. Links Phenomenon elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "linked_zones": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Other zones that are associated with the zone. Links Zone elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "populations": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Distinct collective groups or communities residing within the zone. Links Collective elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "titles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Titles assigned to represent, manage, or protect the zone. Links Title elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "principles": {"type": ["array", "null"], "items": {"type": "string", "format": "uuid"}, "description": "Influential mechanics acted within, upon, or by the zone. Links Construct elements; replaces the whole list (null is the same as []). Every id must name an existing one in this world."}, "world": {"description": "Tolerated and ignored: the API-Key decides the world."}, "created_by": {"description": "Tolerated and ignored: the server records who created an element."}, "changed_by": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}, "changed_via": {"description": "Tolerated and ignored: the server records who last changed an element, and through which door."}}, "patternProperties": {"^(atlas_|shadow_|x_)": {"description": "Extension field, stored opaquely and returned verbatim (any JSON value; at most 65536 bytes of extensions per element, counted as compact UTF-8 JSON)."}}, "additionalProperties": false}, "ZoneList": {"title": "ZoneList", "type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Zone"}}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Pass back as `cursor` for the next page; null on the last."}}, "required": ["data", "has_more", "next_cursor"]}, "ElementStub": {"title": "ElementStub", "type": "object", "description": "What `?expand=` puts in place of a linked id.", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "supertype": {"type": "string"}, "subtype": {"type": "string"}, "image_url": {"type": "string"}}, "required": ["id", "name", "supertype", "subtype", "image_url"]}, "LinkOperation": {"title": "LinkOperation", "type": "object", "description": "Atomic add/remove on one multi-link field. Adds dedupe; removes of absent ids are tolerated. Added ids must exist in this world.", "properties": {"add": {"type": "array", "items": {"type": "string", "format": "uuid"}, "default": []}, "remove": {"type": "array", "items": {"type": "string", "format": "uuid"}, "default": []}}}, "SearchResults": {"title": "SearchResults", "type": "object", "description": "The elements whose name or text holds every word of `q`.", "properties": {"query": {"type": "string", "description": "`q` as searched (trimmed)."}, "results": {"type": "array", "items": {"type": "object", "properties": {"type": {"type": "string", "enum": ["ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"]}, "id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "supertype": {"type": "string"}, "subtype": {"type": "string"}, "field": {"type": "string", "description": "Where the words were found: `name`, or the first text field (in the type's schema order) that holds them all, else the first that holds any."}, "snippet": {"type": "string", "maxLength": 160, "description": "Up to 160 characters of that field around the first match, as plain text (runs of whitespace shown as one space, no markup)."}, "known_of": {"type": "boolean", "const": true, "description": "Present, and true, only when this reader knows of the element but does not see it whole (the element's own `known_of`): it matched only on what such a row keeps (its name, description and its type's public fields). Absent: the element is whole."}}, "required": ["type", "id", "name", "supertype", "subtype", "field", "snippet"], "additionalProperties": false}, "description": "Name matches first, then the rest; inside each, by type (the API's type order), then alphabetical by name."}, "count": {"type": "integer", "description": "How many results came back."}, "truncated": {"type": "boolean", "description": "True when more elements matched than `limit` let through. There are no further pages: narrow `q` or `type`, or raise `limit`."}}, "required": ["query", "results", "count", "truncated"], "additionalProperties": false}, "ChangesPage": {"title": "ChangesPage", "type": "object", "description": "One page of the feed. To sync: start without `since` (or since=0), apply each change in order, pass `cursor` back as `since` until `has_more` is false, then keep polling with it. On 409 resync_required, pull again from since=0.", "properties": {"cursor": {"type": ["string", "integer"], "description": "Pass back as `since`, as is (treat it as opaque)."}, "changes": {"type": "array", "items": {"oneOf": [{"type": "object", "properties": {"op": {"const": "upsert"}, "type": {"type": "string", "enum": ["ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"]}, "id": {"type": "string", "format": "uuid"}, "change_seq": {"type": "integer"}, "updated_at": {"type": ["string", "null"], "format": "date-time"}, "element": {"type": "object", "description": "The element as GET /{type}/{id} returns it (the schema named by `type`, capitalized: Character, Location...)."}}, "required": ["op", "type", "id", "change_seq", "updated_at", "element"], "additionalProperties": false}, {"type": "object", "properties": {"op": {"const": "delete"}, "type": {"type": "string", "enum": ["ability", "collective", "character", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"]}, "id": {"type": "string", "format": "uuid"}, "change_seq": {"type": "integer"}, "deleted_at": {"type": ["string", "null"], "format": "date-time"}, "deleted_by": {"type": ["string", "null"], "format": "uuid", "description": "The world membership that deleted it, or null (the owner, the server, or a delete from before doors were recorded)."}, "deleted_via": {"type": ["string", "null"], "enum": ["seat", "mcp", "declared", "api", "v1", "sync", "server", null], "description": "The door of the delete, as an element's `changed_via`."}, "reason": {"type": "string", "enum": ["deleted", "hidden", "out_of_view"], "description": "Why the element left your copy. `deleted`: it was deleted (a tombstone). `hidden`: the world's owner or a co-builder hid it from you; remove your copy, and never offer to keep it (the row's deleted_at, deleted_by and deleted_via say when, who and through which door it was hidden; a GET of it is 404, never 410). `out_of_view` is reserved: nothing sends it yet."}}, "required": ["op", "type", "id", "change_seq", "deleted_at", "reason"], "additionalProperties": false}]}, "description": "An `upsert` carries the whole element; a `delete` carries only its type, id, deleted_at, and who deleted it through which door (deleted_by, deleted_via). A `delete` also says why (`reason`): `deleted`, or `hidden` for an element hidden from you. A hidden row reaches you only for a hide made after your view last started (you joined, or your epoch last moved), whatever cursor you send. It may still name an id you never held (an element made and hidden while you were not reading): drop it like any delete of an unknown id."}, "has_more": {"type": "boolean"}, "head": {"type": "integer", "description": "The world's current change_seq. A cursor past it is stale (the world was rewound): pull again from since=0."}, "epoch": {"type": ["integer", "null"], "description": "A guest's view epoch (the third part of its cursor), null for everyone else but a contributor's (person or seat), which carries one too. It moves when the reader's view changes with no change_seq move (a Character given or taken, a role, an element hidden from a guest or unhidden): a tip whose epoch differs from the one you last applied means walk again, and the walk answers 409 resync_required. Read it here, never out of the cursor."}, "epoch_reason": {"type": ["string", "null"], "enum": ["role", "view", null], "description": "Why `epoch` last moved, beside it (null when `epoch` is null). `role`: your own role or membership moved; keep what you hold that a walk from since=0 does not name. `view`: what you may see moved (a delete, a hide, an unhide, a Character given or taken); drop it. `view` too for an epoch that never moved. It names the last move only: when your tip is more than one epoch past the one you applied, walk with your cursor, and the 409's `reason` covers every move since it (`role` if any was). A tool never empties a copy on its own."}}, "required": ["cursor", "changes", "has_more", "head", "epoch", "epoch_reason"], "additionalProperties": false}, "World": {"title": "World", "type": "object", "description": "The world named by the credential.", "properties": {"id": {"type": "string", "format": "uuid", "readOnly": true}, "name": {"type": "string", "maxLength": 255}, "description": {"type": "string"}, "image_url": {"type": "string", "maxLength": 1024}, "time_format_names": {"type": "array", "items": {"type": "string"}, "description": "The names of the calendar's steps, e.g. ['Day', 'Week', 'Month', 'Year']."}, "time_format_equivalents": {"type": "array", "items": {"type": ["integer", "string"]}, "description": "How many time_basic_unit each step of time_format_names holds, in the same order, e.g. [1, 7, 30, 365] when the basic unit is Day."}, "time_basic_unit": {"type": "string", "maxLength": 128, "description": "The unit every element date and duration counts in (e.g. 'Year', 'Day'): a birth_date of 1200 is 1200 of these."}, "time_range_min": {"type": ["integer", "null"], "minimum": -2147483648, "maximum": 2147483647, "description": "The earliest point of the world's timeline, in time_basic_unit."}, "time_range_max": {"type": ["integer", "null"], "minimum": -2147483648, "maximum": 2147483647, "description": "The latest point of the world's timeline, in time_basic_unit."}, "time_range_current": {"type": ["integer", "null"], "minimum": -2147483648, "maximum": 2147483647, "description": "The world's present moment, in time_basic_unit."}, "length_unit": {"type": "string", "maxLength": 64, "description": "Free-text label for the numbers counted in the world's length units; nothing converts."}, "mass_unit": {"type": "string", "maxLength": 64, "description": "Free-text label for the numbers counted in the world's mass units; nothing converts."}, "distance_unit": {"type": "string", "maxLength": 64, "description": "Free-text label for the numbers counted in the world's distance units; nothing converts."}, "public_read": {"type": "boolean", "description": "Whether the world's owner opened it to the public: where the public reader is on, anyone reads it with no key (`?world=`). Set by the owner on the world page; not writable here.", "readOnly": true}, "agent_narrator_rule": {"type": "boolean", "description": "Whether a Narrative written through an agent seat is narrated by that seat's own Character, as a guest's is. Set by the world's owner on the world page; not writable here.", "readOnly": true}, "owner_character": {"type": ["string", "null"], "format": "uuid", "description": "The owner's \"this Character is me\": a Character id in this world, or null."}, "created_at": {"type": ["string", "null"], "format": "date-time", "readOnly": true}, "updated_at": {"type": ["string", "null"], "format": "date-time", "description": "Moves when the world's own fields change, not on element writes.", "readOnly": true}}, "required": ["id", "name", "description", "image_url", "time_format_names", "time_format_equivalents", "time_basic_unit", "time_range_min", "time_range_max", "time_range_current", "length_unit", "mass_unit", "distance_unit", "public_read", "agent_narrator_rule", "owner_character", "created_at", "updated_at"]}, "WorldPatch": {"title": "WorldPatch", "type": "object", "description": "Only the fields to change. Any other key, a read-only one included, is a 422.", "properties": {"name": {"type": "string", "maxLength": 255, "minLength": 1}, "description": {"type": "string"}, "image_url": {"type": "string", "maxLength": 1024}, "time_format_names": {"type": "array", "items": {"type": "string"}, "description": "The names of the calendar's steps, e.g. ['Day', 'Week', 'Month', 'Year']."}, "time_format_equivalents": {"type": "array", "items": {"type": "string"}, "description": "How many time_basic_unit each step of time_format_names holds, in the same order, e.g. [1, 7, 30, 365] when the basic unit is Day."}, "time_basic_unit": {"type": "string", "maxLength": 128, "description": "The unit every element date and duration counts in (e.g. 'Year', 'Day'): a birth_date of 1200 is 1200 of these."}, "time_range_min": {"type": ["integer", "null"], "minimum": -2147483648, "maximum": 2147483647, "description": "The earliest point of the world's timeline, in time_basic_unit."}, "time_range_max": {"type": ["integer", "null"], "minimum": -2147483648, "maximum": 2147483647, "description": "The latest point of the world's timeline, in time_basic_unit."}, "time_range_current": {"type": ["integer", "null"], "minimum": -2147483648, "maximum": 2147483647, "description": "The world's present moment, in time_basic_unit."}, "length_unit": {"type": "string", "maxLength": 64, "description": "Free-text label for the numbers counted in the world's length units; nothing converts."}, "mass_unit": {"type": "string", "maxLength": 64, "description": "Free-text label for the numbers counted in the world's mass units; nothing converts."}, "distance_unit": {"type": "string", "maxLength": 64, "description": "Free-text label for the numbers counted in the world's distance units; nothing converts."}, "owner_character": {"type": ["string", "null"], "format": "uuid", "description": "The owner's \"this Character is me\": a Character id in this world, or null."}}, "additionalProperties": false}, "BulkResult": {"title": "BulkResult", "type": "object", "properties": {"errors": {"type": "boolean", "description": "True when any item failed."}, "items": {"type": "array", "description": "One result per request item, in the same order.", "items": {"type": "object", "properties": {"status": {"type": "integer", "description": "201 created, 200 replaced, or the item's error status."}, "id": {"type": ["string", "null"], "format": "uuid"}, "created_at": {"type": ["string", "null"], "format": "date-time"}, "updated_at": {"type": ["string", "null"], "format": "date-time"}, "error": {"type": "object", "properties": {"type": {"type": "string", "enum": ["api_error", "authentication_error", "idempotency_error", "invalid_request", "not_found", "permission_error", "rate_limited"]}, "code": {"type": "string", "enum": ["already_member", "api_error", "apply_failed", "ask_failed", "ask_unavailable", "character_hidden", "character_taken", "file_too_large", "files_full", "gone", "guest_not_supported", "id_conflict", "idempotency_error", "idempotency_in_flight", "invalid_credentials", "invalid_link", "invalid_request", "key_revoked", "known_of_not_writable", "media_unavailable", "not_author", "not_found", "owner_only", "payload_too_large", "permission_error", "pin_required", "player_role_fixed", "precondition_failed", "quota_exceeded", "rate_limited", "removal_in_flight", "resync_required", "server_busy", "storage_full", "too_many_requests", "upload_pending", "view_as_owner_only", "world_full"], "description": "Stable, machine-readable (e.g. invalid_link, id_conflict)."}, "message": {"type": "string"}, "param": {"type": ["string", "null"], "description": "The offending field or parameter, when there is one."}, "doc_url": {"type": "string"}}, "required": ["type", "code", "message", "param", "doc_url"], "description": "Only on a failed item: the same object a standalone error's `error` holds."}}, "required": ["status", "id"], "additionalProperties": false}}}, "required": ["errors", "items"], "additionalProperties": false}, "SeatMade": {"properties": {"id": {"description": "The seat: its membership id, as GET /members and the roster name it.", "format": "uuid", "title": "Id", "type": "string"}, "name": {"description": "The AI's own name in the world.", "title": "Name", "type": "string"}, "role": {"description": "co_builder, contributor or guest.", "title": "Role", "type": "string"}, "kind": {"description": "Always agent: a seat has no account of its own.", "title": "Kind", "type": "string"}}, "required": ["id", "name", "role", "kind"], "title": "SeatMade", "type": "object"}, "SeatWorld": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}}, "required": ["id", "name"], "title": "SeatWorld", "type": "object"}, "SeatCreated": {"properties": {"world": {"$ref": "#/components/schemas/SeatWorld"}, "seat": {"$ref": "#/components/schemas/SeatMade"}, "character": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "The Character the seat plays, or null for none. With character mine, the same Character your own membership plays: two rows, one Character.", "title": "Character"}, "key": {"description": "The seat's write key (API-Key). Shown in this answer only.", "title": "Key", "type": "string"}, "pin": {"description": "The seat's secret, an ow_s_ value it sends as API-Pin where a person sends their PIN. Shown in this answer only.", "title": "Pin", "type": "string"}, "env": {"description": "The lines of a .env file for the AI: OW_API_KEY, OW_API_PIN, OW_WORLD, OW_CHARACTER when it plays one, OW_API_BASE. The same lines POST /join answers with.", "title": "Env", "type": "string"}}, "required": ["world", "seat", "character", "key", "pin", "env"], "title": "SeatCreated", "type": "object"}, "SeatRow": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"description": "The AI's own name in the world.", "title": "Name", "type": "string"}, "role": {"description": "co_builder, contributor or guest.", "title": "Role", "type": "string"}, "kind": {"description": "Always agent.", "title": "Kind", "type": "string"}, "character": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "The Character the seat plays, or null.", "title": "Character"}, "sponsor": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Who answers for the seat: a username when that person is the world's owner or one of its members; null for a seat from an email invite, or when the sponsor has left.", "title": "Sponsor"}, "joined_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Joined At"}, "last_active": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "The latest use of the seat's key, or null.", "title": "Last Active"}}, "required": ["id", "name", "role", "kind", "character", "sponsor", "joined_at", "last_active"], "title": "SeatRow", "type": "object"}, "SeatList": {"properties": {"data": {"items": {"$ref": "#/components/schemas/SeatRow"}, "title": "Data", "type": "array"}}, "required": ["data"], "title": "SeatList", "type": "object"}, "AccountInvite": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "email": {"title": "Email", "type": "string"}, "role": {"description": "co_builder, contributor or guest.", "title": "Role", "type": "string"}, "created_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Created At"}}, "required": ["id", "email", "role", "created_at"], "title": "AccountInvite", "type": "object"}, "AccountMember": {"properties": {"id": {"description": "The membership; DELETE or PATCH it by this id.", "format": "uuid", "title": "Id", "type": "string"}, "username": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The member's username; null for an agent seat.", "title": "Username"}, "role": {"description": "co_builder, contributor or guest.", "title": "Role", "type": "string"}, "joined_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "default": null, "title": "Joined At"}, "last_active": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "default": null, "title": "Last Active"}, "roster_character": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "default": null, "description": "The Character this row plays, or null. One Character can be on two rows: a member's own, and the seat of their AI made with character mine.", "title": "Roster Character"}, "kind": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "description": "agent on an agent seat's row, player on a player seat's (no account, read only, a guest); absent on a person's.", "title": "Kind"}, "agent_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "description": "An agent seat's own name.", "title": "Agent Name"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "description": "A player seat's own name.", "title": "Name"}, "for_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "description": "An agent seat from an email invite: the address you typed, never which account it matched; else empty.", "title": "For Email"}, "sponsor": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "description": "An agent seat's sponsor, the person who answers for it: their username when they are you or a member of this world; null for a seat from an email invite (the account behind an address stays hidden) or a sponsor who has left.", "title": "Sponsor"}, "may_change_character": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "default": null, "description": "An agent seat only: true when the owner gave this seat its Character, or its sponsor could change it; then the seat may change, never delete, that Character. False says only that no such grant is held: a co-builder seat changes everything, and any seat changes what it made. Absent on a person's row.", "title": "May Change Character"}}, "required": ["id", "username", "role"], "title": "AccountMember", "type": "object"}, "AccountMembers": {"properties": {"members": {"items": {"$ref": "#/components/schemas/AccountMember"}, "title": "Members", "type": "array"}, "invites": {"items": {"$ref": "#/components/schemas/AccountInvite"}, "title": "Invites", "type": "array"}}, "required": ["members", "invites"], "title": "AccountMembers", "type": "object"}}, "securitySchemes": {"ApiKey": {"type": "apiKey", "in": "header", "name": "API-Key", "description": "Which world: an `ow_r_`/`ow_w_` key, an agent seat's key, or a classic 10-digit key. Try `0000000001`, the public demo world (Moppetopia), for reads."}, "ApiPin": {"type": "apiKey", "in": "header", "name": "API-Pin", "description": "Proves it's you on writes: your 4-digit account PIN, or an agent seat's `ow_s_` secret."}, "AccountToken": {"type": "http", "scheme": "bearer", "description": "An `ow_a_` account token, for the /account routes."}, "SessionToken": {"type": "apiKey", "in": "header", "name": "X-Session-Token", "description": "The session token a sign-in through the app client (`/api/v2/auth/app/v1/auth/login`) answers with. Accepted on every /account route; meant for turning that sign-in into the device's own `ow_a_` token at `POST /account/tokens`, which a tool then carries instead."}}, "headers": {"RateLimit-Limit": {"description": "The request budget: requests this credential may make in a one-minute window (per key or account token; per address for a request with neither).", "schema": {"type": "integer"}}, "RateLimit-Remaining": {"description": "Requests left in this window; never below 0.", "schema": {"type": "integer"}}, "RateLimit-Reset": {"description": "Seconds until this window ends and the budget is whole again (1 to 60).", "schema": {"type": "integer"}}}}, "servers": [{"url": "https://www.onlyworlds.com", "description": "OnlyWorlds"}], "tags": [{"name": "World", "description": "The world your key names, who you are in it, and its members."}, {"name": "Changes", "description": "The sync feed: every write to the world, in order."}, {"name": "Bulk", "description": "Many writes of mixed types in one request."}, {"name": "Search", "description": "Find elements by the words in their name and text."}, {"name": "Join", "description": "A tool or AI agent redeems a join code for a seat."}, {"name": "Media", "description": "Tickets to upload or remove an image."}, {"name": "Account", "description": "Your account, worlds, keys, members and invites, with an `ow_a_` account token."}, {"name": "Health", "description": "Is the service up. No credentials."}]}