Skip to content

Keys and PINs

A world key names one world and says what the caller may do in it. The PIN proves the caller may write. Both are managed in the account portal, where keys are minted and revoked.

API-Key: {key}
API-Pin: {pin}

The header names are exactly API-Key and API-Pin. The key alone determines the world, so no request passes a world id.

Key What it is Reads Writes
ow_w_… World write key Key alone Key and PIN
ow_r_… World read key Key alone Refused: 403 permission_error
10-digit legacy key The original world key Key alone; a private world also needs the PIN Key and PIN
ow_a_… Account token Acts on your account, not on one world: see Account Tokens
  • Each world key (ow_w_, ow_r_, legacy) is scoped to one world.
  • Legacy 10-digit keys keep working and stay valid. New ones are no longer issued: mint a prefixed key instead.
  • A new key is shown once, when it is minted. Key lists afterwards show only its last four characters.
  • ow_r_ keys are made for sharing: hand one to players or readers and they can read the world without any second secret.
  • Both the current API and the Classic API accept prefixed and legacy keys.

The legacy keys 0000000000 to 0000000009 are reserved for demo worlds. They are read-only on every route (a write is 403 permission_error) and read without a PIN. These answer today:

Key World Try
0000000000 Hyperion, the public example world GET /api/v2/character?fields=id,name
0000000001 Moppetopia GET /api/v2/character?name__icontains=admiral

The rest of the range is not for public use; unassigned keys answer 401 invalid_credentials.

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 world with a PIN requires it on every write. New worlds always have one. An account without a PIN chooses it when creating its first world, and changing it in account settings changes it on every world the account owns.
  • Reads with a prefixed key (ow_w_, ow_r_) never need the PIN. Only a legacy 10-digit key reading a private world must send it.
  • Failed PIN attempts are throttled. Too many answer 429 rate_limited with a Retry-After header, and repeated failures escalate to a temporary lockout.

Creating a world through the API with an account token: an account without a PIN sends pin (4 digits, 1000 to 9999) with its first world; an account that has one sends name only, and a pin alongside it is a 422.

A world can have members besides its owner (see Members and Sharing). A member mints their own keys for the world in their own account, and a member’s key writes with the member’s own account PIN, never the owner’s. A member without an account PIN cannot accept an invite or mint a write key: 409 pin_required.

Removing a member, or a member leaving, deletes that member’s keys for the world.

An AI agent that joins a world through an agent link receives, once, an ow_w_ key and a seat secret (ow_s_…). The agent sends the secret as API-Pin on writes. It is never the world’s PIN or any account’s PIN, and reads take the key alone. Joining is described on AI Agents.

GET /api/v2/me answers who the calling key is, for any key and without a PIN:

Terminal window
curl -s "https://www.onlyworlds.com/api/v2/me" -H "API-Key: {key}"
{ "world": { "id": "0695…", "name": "Hyperion" }, "scope": "write", "kind": "owner",
"role": "owner", "membership": null, "character": "0698…" }

GET /api/v2/world also validates a key: a 200 means it is accepted.

No route checks a PIN without a write. /me and every read take the key alone (except a legacy key reading a private world), so a 200 there confirms the key, not the PIN. The PIN is checked on the first write, and a wrong PIN answers the same 401 invalid_credentials as a bad key. The message tells them apart:

Answer message Meaning
401 invalid_credentials No valid API-Key. The key is missing or unknown
401 invalid_credentials Incorrect PIN. The key is valid; the PIN is missing or wrong
401 key_revoked The key was recognized but revoked
403 permission_error This key is read-only and cannot write to this world. The key is genuine but lacks the scope, such as a read key on a write route
429 rate_limited Too many failed PIN attempts. Try again later. Too many wrong PINs: wait the Retry-After seconds

Message wording may change: branch on code, and use the message only to tell a person what to fix.

  • A key and PIN in browser code are visible to anyone who opens the page. Ship only an ow_r_ read key in a public page, or ask each visitor for their own credentials at runtime. See CORS.
  • Keep keys and PINs in a .env file that git ignores, never in a commit or a chat.
  • An MCP client stores the API-Key and API-Pin headers in its own configuration as written. Use a key you can revoke on its own. See MCP Server.
  • An agent seat’s secret is shown once, at join. See AI Agents.
  • A leaked key is revoked in the account portal; mint a new one in its place.

An ow_a_ token acts on your account: it lists your worlds, creates worlds, mints and revokes world keys, and manages invites, members and watched worlds. Mint one in the account portal under Settings → Account tokens, or with POST /api/v2/account/tokens. Like a key, it is shown once.

It is sent as a bearer token, not as API-Key:

Terminal window
curl -s "https://www.onlyworlds.com/api/v2/account/worlds" \
-H "Authorization: Bearer ow_a_…"
Method Route Purpose
GET, PATCH /api/v2/account Your profile
GET /api/v2/account/worlds The worlds you own and the worlds shared with you
POST /api/v2/account/worlds Create a world: 201 with the world and a new ow_w_ key, shown once
POST /api/v2/account/worlds/{world_id}/keys Mint a world key, shown once
GET /api/v2/account/worlds/{world_id}/keys List a world’s keys (last four characters only)
DELETE /api/v2/account/worlds/{world_id}/keys/{key_id} Revoke a world key
POST /api/v2/account/tokens Mint an account token, shown once
GET /api/v2/account/tokens List your account tokens (last four characters only)
DELETE /api/v2/account/tokens/{token_id} Revoke an account token

Minting a world key takes scope ("read" or "write", default "write") and an optional name, a label such as "atlas · my laptop" that is echoed back in the key list. A name over 255 characters is a 422; the same limit applies to account token names.

Terminal window
curl -s -X POST "https://www.onlyworlds.com/api/v2/account/worlds/{world_id}/keys" \
-H "Authorization: Bearer ow_a_…" -H "Content-Type: application/json" \
-d '{ "scope": "read", "name": "players" }'
{ "id": "…", "name": "players", "last4": "…", "scope": "READ",
"last_used_at": null, "revoked": false, "key": "ow_r_…" }

key appears in this response only. Creating a world answers its summary with a new write key the same way:

Terminal window
curl -s -X POST "https://www.onlyworlds.com/api/v2/account/worlds" \
-H "Authorization: Bearer ow_a_…" -H "Content-Type: application/json" \
-d '{ "name": "Hyperion" }'
{ "id": "…", "name": "Hyperion", "role": "owner", "public_read": false,
"keys": [ { "name": "", "last4": "…", "scope": "WRITE", "revoked": false } ],
"key": "ow_w_…" }

The routes for invites, members and watched worlds are on Members and Sharing. A missing or invalid account credential answers 401.