Your first hour
This page takes you from nothing to a working read and write, with no account. You read Moppetopia, a public sample world, then write to the practice world, a world anyone may write to. Every example on this page runs as written. Pick a language once and the page keeps it.
1. Install
Section titled “1. Install”TypeScript needs Node 18 or later. The package is ESM, so save each example as a .mjs file.
npm install @onlyworlds/sdkPython needs 3.12 or later. The package has no other dependencies.
pip install onlyworldsWith curl there is nothing to install. Every request goes to https://www.onlyworlds.com/api/v2/ with the key in the API-Key header.
2. Read a world
Section titled “2. Read a world”0000000001 is a public demo key. It reads Moppetopia and needs no PIN. The world’s own record carries counts, how many elements it holds in all and per type.
In TypeScript, save this as read.mjs and run node read.mjs:
import { OwV2Client } from '@onlyworlds/sdk';
// 0000000001 is a public demo key: it reads the sample world Moppetopia, no PIN
const client = new OwV2Client({ apiKey: '0000000001' });
const world = await client.getWorld();
console.log(world.name, 'holds', world.counts.total, 'elements,', world.counts.types.character, 'of them Characters');
const page = await client.list('character', { limit: 5 });
for (const character of page.data) console.log('-', character.name);In Python, save this as read.py and run python read.py:
from onlyworlds import Client
# 0000000001 is a public demo key: it reads the sample world Moppetopia, no PIN
client = Client("0000000001")
world = client.get_world()
counts = world["counts"]
print(world["name"], "holds", counts["total"], "elements,", counts["types"]["character"], "of them Characters")
page = client.list_page("character", limit=5)
for character in page["data"]:
print("-", character["name"])With curl:
curl -H "API-Key: 0000000001" "https://www.onlyworlds.com/api/v2/world/"
curl -H "API-Key: 0000000001" "https://www.onlyworlds.com/api/v2/character/?limit=5&fields=id,name"The first answer is the world, with counts among its keys. The second is a page of Characters: {"data": [...], "has_more": true, "next_cursor": "..."}. Pass next_cursor back as ?cursor= for the next page.
It prints something like this (the world changes now and then, so the numbers can differ):
Moppetopia holds 501 elements, 23 of them Characters
- Lieutenant Jonas
- Wrinkles
- Governor Threadsworth
- Old Biscuit
- Commander VexEvery element type has the same routes and calls at its singular name: location, event, creature and the rest of the 22 types. Each type’s page lists its fields and what each link points to, such as Creature.
3. Write, read back, delete
Section titled “3. Write, read back, delete”A write needs a write key and a PIN. These two are the practice world’s. It goes back to the same copy every night at 03:00 UTC, takes 60 requests a minute from one address, and anyone can read what you put in it, so put nothing private there. Keep them out of your code, in the environment:
export OW_API_KEY=ow_w_QRRMlAFPF9vPZSqmiV6QJm2HDJH_sP28x2RccPmYP1A
export OW_API_PIN=1234In Windows PowerShell, set each as $env:OW_API_KEY = "ow_w_...".
The example makes a Location and a Character who lives there. A link field holds the id of the element it points to. Then it changes the Character’s description, reads the Character back, deletes both, and shows what a read of a deleted element answers.
In TypeScript, save this as write.mjs and run node write.mjs:
import { OwV2Client, OwApiError } from '@onlyworlds/sdk';
const writer = new OwV2Client({ apiKey: process.env.OW_API_KEY, apiPin: process.env.OW_API_PIN });
// make two elements and link them: a link is the id of the element it points to
const harbour = await writer.create('location', { name: 'Saltmarsh Harbour' });
const keeper = await writer.create('character', { name: 'Wren the Lamplighter', location: harbour.id });
console.log('made', keeper.name, keeper.id);
// change a field: a patch sends only the fields it changes
await writer.patch('character', keeper.id, { description: 'Keeps the harbour lamps lit.' });
// read it back
const again = await writer.get('character', keeper.id);
console.log(again.name, 'lives at', again.location === harbour.id ? harbour.name : again.location);
console.log(again.description);
// delete both; a read of a deleted element answers 410 gone
await writer.delete('character', keeper.id);
await writer.delete('location', harbour.id);
try {
await writer.get('character', keeper.id);
} catch (err) {
if (err instanceof OwApiError) console.log('after the delete:', err.status, err.code);
else throw err;
}In Python, save this as write.py and run python write.py:
import os
from onlyworlds import ApiError, Client
writer = Client(os.environ["OW_API_KEY"], os.environ["OW_API_PIN"])
# make two elements and link them: a link is the id of the element it points to
harbour = writer.create("location", {"name": "Saltmarsh Harbour"})
keeper = writer.create("character", {"name": "Wren the Lamplighter", "location": harbour["id"]})
print("made", keeper["name"], keeper["id"])
# change a field: a patch sends only the fields it changes
writer.patch("character", keeper["id"], {"description": "Keeps the harbour lamps lit."})
# read it back
again = writer.get("character", keeper["id"])
print(again["name"], "lives at", harbour["name"] if again["location"] == harbour["id"] else again["location"])
print(again["description"])
# delete both; a read of a deleted element answers 410 gone
writer.delete("character", keeper["id"])
writer.delete("location", harbour["id"])
try:
writer.get("character", keeper["id"])
except ApiError as err:
print("after the delete:", err.status, err.code)With curl:
# make a Character; the answer is the element, with its id
curl -X POST "https://www.onlyworlds.com/api/v2/character/" \
-H "API-Key: $OW_API_KEY" -H "API-Pin: $OW_API_PIN" \
-H "Content-Type: application/json" \
-d '{"name": "Wren the Lamplighter"}'
# change a field (put in the id from the answer): a patch sends only the fields it changes
curl -X PATCH "https://www.onlyworlds.com/api/v2/character/<id>/" \
-H "API-Key: $OW_API_KEY" -H "API-Pin: $OW_API_PIN" \
-H "Content-Type: application/json" \
-d '{"description": "Keeps the harbour lamps lit."}'
# read it back, then delete it
curl -H "API-Key: $OW_API_KEY" "https://www.onlyworlds.com/api/v2/character/<id>/"
curl -X DELETE "https://www.onlyworlds.com/api/v2/character/<id>/" \
-H "API-Key: $OW_API_KEY" -H "API-Pin: $OW_API_PIN"A create answers 201 with the whole element. A delete answers 204, and a read of the deleted id afterwards answers 410 with the code gone. A read sends the key alone; the PIN goes only on writes. To link two elements, send the other element’s id in a link field, such as "location": "<location-id>".
It prints:
made Wren the Lamplighter 01a1d4ef-8b76-73ea-828f-7f3c1b38fd8e
Wren the Lamplighter lives at Saltmarsh Harbour
Keeps the harbour lamps lit.
after the delete: 410 goneThat is what a tool that reads and writes needs. Four rules cover the rest of the basics:
nameis the one required field. Every other field may be left out.- A patch replaces each field it sends and leaves the rest: a list of links is replaced whole. The packages’
editLinksandedit_linksadd or remove single links instead (Link fields). - The key decides the world. You never send a world id.
- A refusal answers with a code, such as
goneabove, and a link to that code on the errors page.
4. Your own world
Section titled “4. Your own world”When you want a world of your own, sign up at onlyworlds.com, make a world, and mint a key on its page. An ow_r_ key reads and is safe to share; an ow_w_ key reads and writes with your account’s PIN. Put them in OW_API_KEY and OW_API_PIN in place of the practice world’s, and the examples above run against your world. Keys and PINs covers every kind of key.
What you can leave for later
Section titled “What you can leave for later”The rest of these docs cover things a tool needs only when it does them. None of it is needed for the steps above.
- Many writes at once, retries, conflicts: Writes and bulk.
- Following a world as it changes: Changes.
- Sharing a world: members, roles and invite links in Members, and the Hidden elements only some members see.
- AI agents: seats, the MCP server and the context for one Character, under AI agents.
- Signing people in to your tool: Accounts, members and sharing.
- A world as files on disk: World folders.
- Everything a package does: the TypeScript and Python pages, and Unity and the other engines under SDKs.