Hidden elements
The world’s owner and its co-builders can hide an element: a twist the players have not reached, a villain’s real allegiance, the game master’s own notes. A hidden element stays in the world, unchanged. The owner, the co-builders and the member who created it still see it; to everyone else it answers as if it did not exist, and a contributor’s change feed says it was hidden.
The mark is a record beside the element, not a field. Hiding or unhiding leaves the element’s body and its change_seq alone, and nothing in the body says it is hidden.
Who sees a hidden element
Section titled “Who sees a hidden element”| Reader | Sees |
|---|---|
The owner: on onlyworlds.com, with every world key the owner minted (ow_w_, ow_r_), and with a legacy key |
Everything, hidden elements included |
| A co-builder, person or agent seat | Everything, hidden elements included |
| A contributor, person or agent seat | The whole world except the hidden elements, apart from those it created |
| A guest, or a player | Its guest’s view except the hidden elements, apart from those it created |
- Hiding wins over every way a guest is given sight: an element that names its Character, a Narrative its Character narrates or one of its circles holds, a Character on the roster. Only having created the element gives it back.
- An agent seat reads as its role, also when it plays a Character.
- A key you mint is your own tool and shows what you hid. To share a world without what you hid, use a player link or an invite with a role.
What everyone else gets
Section titled “What everyone else gets”To a reader it is hidden from, a hidden element answers exactly as a missing one, byte for byte:
GET /api/v2/{type}/{id}is404not_found, never410gone.- Lists, the
charactersfilter, search, the MCP server’s tools and world index, and every count andhas_moreleave it out. Its files answer404. - Links to it are left out of every body the reader reads: a single link reads
null, a multi link loses the id, and?expandhas nothing to expand. The elements that carry those links do not change: theirchange_seqstays where it was. - A hidden Character’s id reads
nullon the roster (GET /api/v2/members), onGET /api/v2/meand in the world’sowner_character(GET /api/v2/world, and the join preview). That holds for the Character the reader’s own member plays, too, unless that member created it. The world’sETagdiffers for a reader whoseowner_characterreadsnull, so a cached body does not outlive the hide. - A reader following
/changesis told in the feed: see The feed.
Writes that name it
Section titled “Writes that name it”A write by a reader it is hidden from answers as it would for an id that does not exist:
| Write | Answer |
|---|---|
| A link to it, in any write | 400 invalid_link |
PATCH, or a link operation, on it |
404 not_found |
PUT on its id |
404 not_found. Never a second element at that id, never 409 id_conflict |
POST with its id in the body |
404 not_found, never 409 id_conflict |
DELETE |
204, as for any missing id. Nothing is deleted |
A /bulk item with its id |
404 not_found in that item’s slot |
A reader’s write on an element that links to a hidden one keeps that link, as a guest’s write keeps the links it cannot see: a PUT that leaves it out does not remove it.
Playing a hidden Character
Section titled “Playing a hidden Character”A Character someone plays is on every reader’s roster, so a hidden Character can never be picked to play, by anyone, the owner included:
- Setting it as the Character an agent seat or a player plays (
characteronPATCH /api/v2/account/worlds/{world_id}/members/{member_id}, or Plays on the members table) is422character_hidden,paramcharacter. Unhide it first, or pick another. - Accepting an invite with it as
roster_character, making a player link with it ascharacter, and an AI seat made withcharacterminewhile you play it, answer exactly as for a Character that does not exist. - A player link made before the Character was hidden gives the player a fresh Character when it is redeemed, as it does when the Character was deleted.
- A Character played before it was hidden stays played; only the roster’s
nullcovers it.
Hiding and unhiding
Section titled “Hiding and unhiding”| Method | Route | Key | Answer |
|---|---|---|---|
| PUT | /api/v2/{type}/{id}/hidden |
Write key and PIN | 204: hidden |
| DELETE | /api/v2/{type}/{id}/hidden |
Write key and PIN | 204: not hidden |
| GET | /api/v2/world/hidden |
Any key of the owner’s or a co-builder’s | The world’s hidden elements |
curl -s -X PUT "https://www.onlyworlds.com/api/v2/character/{id}/hidden" \
-H "API-Key: {key}" -H "API-Pin: {pin}"- Who. The owner’s keys, and a co-builder’s key, a person’s or an agent seat’s. Any other member key (a contributor’s, a guest’s, a player’s, a seat below co-builder) gets
403permission_erroron all three routes. That answer comes before the id is looked at, so it says nothing about the element. A read key onPUTorDELETEgets403permission_errortoo. - Which element. One of this world’s, of the category in the path; any other id is
404not_found. - The body. None, or
{"fields": []}.fieldsis kept for hiding single fields, which is not built: a non-empty list is422invalid_requestwithparamfields, and any other key is a422naming it. - Repeats. Hiding a hidden element, or unhiding one that is not hidden, answers
204and changes nothing. These routes take noIdempotency-Key.
GET /api/v2/world/hidden lists every hidden element, newest first, with Cache-Control: no-store:
{ "data": [ { "type": "character", "id": "…", "set_at": "…", "set_by": null } ] }| Field | Meaning |
|---|---|
type, id |
The element |
set_at |
When it was hidden |
set_by |
The membership that hid it, or null when the owner did (as in created_by) |
Deleting a hidden element deletes its mark with it, and deleting a world deletes all of its marks. The MCP server has no tool to hide or unhide.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
401 |
invalid_credentials |
A missing or wrong key, or a wrong PIN |
403 |
permission_error |
The key is not the owner’s or a co-builder’s, or a read key on PUT or DELETE |
404 |
not_found |
The element is not in this world as that category |
422 |
invalid_request |
A malformed id; a body that is not JSON, not an object, or anything but an empty fields. param names it |
429 |
rate_limited |
Too many wrong PINs on this key |
429 |
too_many_requests |
Over the request budget, as on every route (Limits) |
The feed
Section titled “The feed”Every delete op on /changes says why, in reason: deleted for an element that was deleted, hidden for one hidden from this key. (out_of_view is reserved; nothing sends it yet.) A hide takes one place in the world’s change sequence, so head moves by one.
| Reader | When an element is hidden from it | When it is unhidden |
|---|---|---|
| A contributor, person or agent seat | One delete op with reason hidden, at the place the hide took |
Its next call answers 409 resync_required |
| A guest or a player that could see it | Its next call answers 409 resync_required; a guest never gets a delete op |
409 again |
| A guest or a player that could not see it | Nothing | Nothing |
| The owner, a co-builder, the member who created it | Nothing: they still see it | Nothing |
The hidden row has the shape of any delete:
{ "op": "delete", "type": "character", "id": "…", "change_seq": 412, "deleted_at": "…",
"deleted_by": "0699…", "deleted_via": "api", "reason": "hidden" }deleted_at is when it was hidden, deleted_by the membership that hid it (null for the owner), and deleted_via the door it came through; a hide made on onlyworlds.com reads server, as every change made there does. The row comes from the mark, never from a tombstone, so a GET of the id stays 404, never 410. A tombstone of an id that is hidden from the reader (an element deleted, made again with the same id, then hidden) is left out of its feed.
What a client does:
- On
reasonhidden: remove the element from the local copy, as for any delete, and do not offer to keep it. A row may name an id you never held (an element made and hidden while you were not reading): drop it as you would any delete of an unknown id. - On
409resync_required: pull the feed again fromsince=0, then follow the error’sreason(What a reset keeps):role(your own role or membership moved), keep what the new walk does not name;view(something left your view: a delete, a hide, an unhide, a Character change), drop it. When both happened since your cursor, the answer isrole. A tool never empties a copy on its own. - A hidden row reaches a reader only for a hide made after its view last started (when it joined, or when its cursor was last reset), whatever cursor it sends. A new member, or one whose cursor was just reset, gets no row for an earlier hide; a walk from the start by a reader whose view started before the hide does carry it.
The contributor’s cursor
Section titled “The contributor’s cursor”A contributor, person or agent seat, carries the cursor a guest carries: three parts, an epoch on every page and on ?head=true, and a ?head=true cursor to pass straight back as since. Unlike a guest, it still receives every delete op.
- Once: a cursor a contributor stored before hidden elements arrived (two parts, or the bare number
?head=trueused to give) answers409resync_required, withreasonroleif the member’s role ever moved, elseview. Pull again fromsince=0, follow thereason, and store the new cursor. - After that, its
epochmoves when an element it regains is unhidden (reasonview), and when the member moves to or from the contributor role (reasonrole). - Every page and
?head=truecarryepoch_reasonbesideepoch: why the epoch last moved,roleorview. It names the last move only; the409’sreasoncovers every move since your cursor. - The owner and co-builders keep the two-part cursor and
epochnull. A three-part cursor from them (a contributor made co-builder) is the same409.
View as
Section titled “View as”The owner can read the world as another reader would, to check what a member, a Character’s player or the public sees before or after a hide. Add as to a read:
curl -s "https://www.onlyworlds.com/api/v2/character/?as=public" \
-H "API-Key: {key}"| Route | Takes as |
|---|---|
GET /api/v2/{type} and GET /api/v2/{type}/{id} |
Yes |
GET /api/v2/search |
Yes |
GET /api/metadata/{type}/ and GET /api/metadata/{type}/sections/ (the choice lists) |
Yes |
Every write, /changes, /members, /me, /world, the files routes and the hidden routes |
No: 422 |
| Value | Reads as |
|---|---|
| A membership id of this world (a person, an agent seat or a player seat) | That member’s own key, its own creations included. A co-builder’s id sees hidden elements, as its key does |
| A Character id of this world | A guest of that Character with no membership: what names it or it narrates, its circles, the roster. Nothing counts as its own creation |
public |
Everything that is not hidden: no creations, no grants |
A membership id is tried first. The answer is what that reader’s own key would get: links in the bodies, ?expand and the characters filter follow the same reader.
- Who. Only a key with no member: the owner’s own keys and a legacy key. A member key that sends
asgets403view_as_owner_only. - A hidden element is
404not_found, never410. A deleted element is410goneas a co-builder or a contributor; as a guest, a player, a Character or the public it is404. The public reads no tombstones. - Refused.
ason a route that does not take it, or a value that is not a live membership, a Character of this world orpublic, is422invalid_requestwithparamas. The route is checked before the key, so a member key sendingasto a route that does not take it gets the422. The metadata routes answer their refusals in their own shape,{"error": {"code": …, "detail": …}}, with noparam.
On onlyworlds.com, the owner’s world page has a View as picker (me, a player, a Character, anyone). The world page, the type lists and the element pages then show that reader’s view, read-only, under a bar that says who is shown; a hidden element shows the not-found page.
What the mark does not reach
Section titled “What the mark does not reach”- Published share pages. A page that already holds the element keeps showing it: unpublish the page, or republish it without the element.
- The owner’s copies. The owner’s export file, and the owner’s walk of
/changes, carry hidden elements like any other, with nothing in the body to mark them.
On onlyworlds.com, the owner hides and unhides from the element’s page: see onlyworlds.com.
