Writes and Bulk
Writes take a write key (ow_w_, a legacy key, a member’s or an agent seat’s key) and the PIN in API-Pin. The PIN is a 4-digit number (1000 to 9999) set on your account in account settings, and it guards writes to every world you own; a member writes with their own account PIN, and an agent seat sends its seat secret (ow_s_…) as API-Pin. A read key on a write route is 403 permission_error. The key names the world, so a body never carries one.
Create
Section titled “Create”POST /api/v2/{type} creates an element and returns 201 with the full element.
curl -s -X POST "https://www.onlyworlds.com/api/v2/location" \ -H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \ -d '{ "name": "The Time Tombs", "description": "Structures on Hyperion that move backward through time." }'- You may supply an
id: any RFC 4122 UUID is accepted, and UUIDv7 is recommended. Omit it and the server mints a UUIDv7. - An
idthat already exists is409id_conflict. Element ids are unique across all worlds, so the message says which case it is: the id exists in this world (usePUTto upsert), or in another world (mint a new id).
Upsert
Section titled “Upsert”PUT /api/v2/{type}/{id} creates the element if absent and replaces it whole if present: fields left out are reset to empty. It returns 201 when it created and 200 when it replaced. The path decides the id; an id in the body is dropped. An id held by an element in another world is 409 id_conflict.
Partial Update
Section titled “Partial Update”PATCH /api/v2/{type}/{id} changes only the fields sent and returns 200 with the full element.
- Omitted fields are left untouched.
- Arrays replace. A
PATCHtofriendssets the whole list; to add or remove single ids, use the link operations route. - Extension fields merge by key.
- To clear a field, send its empty shape:
| Kind | Clear with |
|---|---|
| Text | "" or null (stored as "") |
| Number | null |
| Single link | null |
| Multi link | [] |
Delete
Section titled “Delete”DELETE /api/v2/{type}/{id} returns 204. It is idempotent: deleting an element that is already gone also returns 204. Deleting an element removes its id from every other element’s links, and the change feed records the delete as a tombstone.
Field Rules
Section titled “Field Rules”nameis required onPOSTandPUT: the key must be present, and an empty string is accepted.- Unknown fields are a
422naming the field. A misspelled field fails loudly instead of vanishing. - Extension fields under the namespaces
atlas_*,shadow_*andx_*are accepted, stored as written and returned verbatim, up to 65,536 bytes of extensions per element (counted as compact UTF-8 JSON). More is a422withparamextensions. - Server fields
type,created_at,updated_atandchange_seqappear on reads and are a422on writes. Strip them before sending a read body back. - Never write back a body read with
?expand=. Its links hold stub objects instead of ids, and a link field takes only ids. Read withoutexpandwhen you mean to send the body back. created_byandworldin a body are ignored, whatever their value.- Text length:
nameholds up to 255 characters,supertypeandsubtype128,image_url1024. Longer is a422. - Values are coerced where they can be: an integer field accepts
"7"as 7, truncates7.9to 7, and readstrueas 1; a value that cannot become an integer is a422. Text fields turn a number into its digits. Send the types the schema names.
Idempotency
Section titled “Idempotency”POST /api/v2/{type} and POST /api/v2/bulk accept an Idempotency-Key header (up to 200 characters). The first successful response is stored for 24 hours under that key:
- An identical replay returns the stored response, with an
Idempotent-Replay: trueheader, and does not write again. - The same key with a different body is
409idempotency_error. - Only successful (
2xx) responses are stored; errors never are. A/bulkanswer is a200even when items failed, so it is stored, item errors included, and a replay returns the same errors.
The rule for retries:
- The answer was lost (a timeout, a dropped connection): retry with the same
Idempotency-Key. A stored2xxreplays; after an error, the request runs again. - A
/bulkanswer haderrors: true: fix the failed items and send them with a new key. Under the same key, an identical body replays the same errors, and a changed body is a409idempotency_error.
curl -s -X POST "https://www.onlyworlds.com/api/v2/character" \ -H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \ -H "Idempotency-Key: 6f1c2a9e-3b7d-4e0a-9c55-2f8d1b4a7e10" \ -d '{ "id": "0199a1c2-7d3e-7f00-8a11-3c5e7b9d2f40", "name": "The Consul" }'The store suppresses replays on a best-effort basis; it is not a ledger. Minting the id on the client keeps a retry safe even without the header: two concurrent creates without client ids can make two elements.
POST /api/v2/bulk creates and upserts up to 1000 elements of mixed categories in one call.
{ "items": [ { "type": "institution", "element": { "id": "0199a1c2-…", "name": "The Hegemony" } }, { "type": "character", "element": { "id": "0199a1c3-…", "name": "The Consul", "institutions": ["0199a1c2-…"] } } ], "atomic": false}{ "errors": false, "items": [ { "status": 201, "id": "0199a1c2-…", "created_at": "…", "updated_at": "…" }, { "status": 201, "id": "0199a1c3-…", "created_at": "…", "updated_at": "…" } ]}- An item whose
elementcarries anidupserts that id; one without anidcreates. Anopfield on an item is optional and ignored. - The answer is HTTP
200once the batch is read. Results are per item, in request order, and eachstatusis what a single write would have returned (201,200, or an error status). - Partial success is the default: one bad item does not stop the rest. Send
"atomic": truefor all or nothing. In atomic mode, iferrorsistruenothing was written, even though the items that would have succeeded show201or200. - A failed item carries the standard error envelope under
error; the top-levelerrorsistrueif any item failed. The codes seen per item are listed under Bulk Errors. - Links are checked against the world plus the batch’s surviving items, in any order, so a batch may reference its own items without sorting. All checks run before any write, and an item that fails also fails the items linking to it.
- Each success echoes the server’s
created_atandupdated_at, so a sync client can set its baseline from the bulk response alone. - A malformed request (not valid JSON,
itemsnot an array, over 1000 items) or an auth failure answers with its own status and the error envelope, not200.
The World
Section titled “The World”PATCH /api/v2/world updates the world’s own fields. It takes a write key and PIN and is owner only: a member’s key, even a co-builder’s, gets 403 owner_only.
| Field | Type |
|---|---|
name, description, image_url, time_basic_unit |
String |
time_format_names, time_format_equivalents |
List of strings |
time_range_min, time_range_max, time_range_current |
Integer or null |
owner_character |
A Character id in this world (the Character that is the owner), or null |
curl -s -X PATCH "https://www.onlyworlds.com/api/v2/world" \ -H "API-Key: {key}" -H "API-Pin: {pin}" -H "Content-Type: application/json" \ -d '{ "time_basic_unit": "Year", "time_range_current": 2732 }'- Unknown fields are a
422, and the whole patch is refused before anything is written. - The world’s
namecannot be empty, unlike an element’s. public_readis set in the account portal and the PIN in account settings, not here.- Every field sent is applied, so an identical value still moves
updated_at. Send only real changes. - World fields do not appear in
/changes. To follow them, pollGET /api/v2/worldand compareupdated_at.
