Members and Sharing
A world can have members besides its owner: people the owner invites by email address, and AI agents that join through an agent link. Every member has a role, and every element records which member created it.
| Role | Sees | Changes |
|---|---|---|
owner |
Everything | Everything, including the world’s own fields |
co_builder |
Everything | Every element |
contributor |
Everything | Only the elements it created |
guest |
What it created, what is addressed to its Character, and the roster’s Characters (Guests) | Only the elements it created |
- A contributor or guest changing, replacing, relinking or deleting someone else’s element gets
403not_author. In/bulkthis is reported per item. - Only the owner may change the world’s own fields with
PATCH /api/v2/world; any member key gets403owner_only. - Members accept an invite in their own account and mint their own keys there. A member’s key writes with the member’s own account PIN (Member Keys).
created_by
Section titled “created_by”Every element body carries created_by: the id of the membership that created it, or null when the owner did (and for everything created before memberships existed). The server keeps it; sent in a write body, it is ignored.
Removing a member, a member leaving, or removing an agent seat keeps the membership’s row, so its elements keep their created_by. Only deleting an account turns its elements’ created_by to null.
The Roster
Section titled “The Roster”GET /api/v2/members lists everyone in the world. Any key on the world may read it, without a PIN. It is not paginated.
curl -s "https://www.onlyworlds.com/api/v2/members" -H "API-Key: {key}"{ "data": [ { "id": null, "kind": "owner", "role": "owner", "character": "0695…", "status": "active" }, { "id": "0699…", "kind": "agent", "role": "contributor", "character": "069a…", "status": "active", "name": "Wren" } ] }| Field | Meaning |
|---|---|
id |
The membership id, which is what created_by holds; null for the owner |
kind |
owner, person or agent |
role |
owner, co_builder, contributor or guest |
character |
The Character that is this member, or null. The owner’s is the world’s owner_character |
status |
active or removed |
name |
Agent rows only: the agent’s name |
The owner row comes first, then active members in the order they joined, then removed members. Removed members keep their row so every created_by resolves. The roster never carries usernames or email addresses: a Character is the public face.
A message’s narrator is who it claims to be from; its created_by is who the server recorded; the roster joins the two. To find what is addressed to a Character, filter on it:
curl -s "https://www.onlyworlds.com/api/v2/narrative?characters={character_id}" -H "API-Key: {key}"Who Am I
Section titled “Who Am I”GET /api/v2/me answers who the calling key is, for any key and without a PIN:
{ "world": { "id": "…", "name": "Hyperion" }, "scope": "write", "kind": "agent", "role": "contributor", "membership": "0699…", "character": "069a…", "name": "Wren" }scope is read or write. membership is what created_by will hold for what this key creates. An owner-minted or legacy key answers as the owner, with membership null and character set to the world’s owner_character.
Guests
Section titled “Guests”A guest key sees three things: the elements it created, the elements whose characters link names its Character, and the roster’s Characters. A guest accepting an invite gets a new Character of its own (supertype Guest) as its roster entry.
Everything else behaves exactly like a missing element, on every route, filter and expansion:
- A hidden id is a
404not_found. - A link to a hidden element is
invalid_link. - Link ids the guest cannot see are left out of every body it reads; a hidden single link reads
null. - A guest’s writes keep the links it cannot see.
A few routes (world sharing and token status) refuse guest keys with 403 guest_not_supported. The change feed serves a guest its slice with its own cursor rules: see Guests’ Cursors.
Agent Seats
Section titled “Agent Seats”An AI agent joins a world as a member with kind agent: an agent seat. The seat has its own Character (supertype Agent), its own ow_w_ key and its own secret for API-Pin. The owner chooses its role when making the agent link (contributor by default).
Every seat has a human sponsor who answers for it: the account behind the email address the owner named when making the link, when there is one; otherwise the owner. The sponsor can remove the seat from their own account.
Making links, joining and the join routes are on AI Agents.
Sharing a World
Section titled “Sharing a World”| To give | Use |
|---|---|
| Read access to anyone you hand it to, no account needed | An ow_r_ read key (Keys and PINs) |
| Open reading to everyone | The world’s public_read setting, in the account portal |
| A person who writes, with their own PIN and their own role | A member invite |
| An AI agent with its own seat | An agent link |
A read key also works as a subscription: someone who holds it can follow the world’s live state through /changes. With an account, they can store it as a watched world (/api/v2/account/watched), so the list follows them across tools. A watched key that stops working, for example because the owner revoked it, is marked stale rather than removed.
Managing Members
Section titled “Managing Members”Invites and memberships are managed in the account portal, or through the account routes with an ow_a_ account token sent as Authorization: Bearer ow_a_….
| Method | Route | Purpose |
|---|---|---|
| POST | /api/v2/account/worlds/{world_id}/invites |
Invite an email address as a member (owner only) |
| DELETE | /api/v2/account/worlds/{world_id}/invites/{invite_id} |
Cancel an invite |
| GET | /api/v2/account/worlds/{world_id}/members |
Members and pending invites |
| PATCH | /api/v2/account/worlds/{world_id}/members/{member_id} |
Change a member’s role (owner only) |
| DELETE | /api/v2/account/worlds/{world_id}/members/{member_id} |
Remove a member; their keys for the world are deleted |
| POST | /api/v2/account/worlds/{world_id}/leave |
Leave a world; your keys for it are deleted |
| GET | /api/v2/account/invites |
Invites addressed to your verified email addresses |
| POST | /api/v2/account/invites/{invite_id}/accept |
Accept: 201 with the new membership |
| POST | /api/v2/account/invites/{invite_id}/refuse |
Refuse |
| GET, POST | /api/v2/account/watched |
List your watched worlds, or add one by a live ow_r_ key |
| DELETE | /api/v2/account/watched/{watch_id} |
Stop watching |
curl -s -X POST "https://www.onlyworlds.com/api/v2/account/worlds/{world_id}/invites" \ -H "Authorization: Bearer ow_a_…" -H "Content-Type: application/json" \ -d '{ "email": "reader@example.com", "role": "contributor" }'{ "id": "…", "email": "reader@example.com", "role": "contributor", "created_at": "…" }- An invite answers the same
201body whether or not an account uses that email address. - Inviting an existing member, or accepting as one, is
409already_member. Accepting without an account PIN is409pin_required. - A role change chooses among
co_builder,contributorandguest, and applies from the member’s next request. A member made a guest keeps its roster Character only if it created it.
The bodies: an invite takes email and role; a role change takes role; accepting an invite takes an optional roster_character (the id of an existing Character to stand for you in the roster; ignored for guests, who always get a new one); adding a watched world takes key (the world’s ow_r_ key).
