onlyworlds (Python)
OnlyWorlds is an open standard for worldbuilding data: 22 element types (characters, locations, events, laws and the rest) that any tool can read and write. This is a Python client for its API, and a reader and writer for OnlyWorlds world folders (a world as a folder of JSON files). Python 3.12+, no runtime dependencies (the optional OKF export needs PyYAML).
Install
pip install onlyworlds
The newest unreleased code: pip install git+https://github.com/OnlyWorlds/python-sdk.
First run
Moppetopia is a public demo world. Its read-only key, 0000000001, needs no account:
from onlyworlds import Client
client = Client("0000000001")
print(client.get_world()["name"])
for c in client.list_page("character", filter={"name__icontains": "fluffington"})["data"]:
print(" ", c["name"])
Moppetopia
Admiral Fluffington
Your own world
Create a world at onlyworlds.com; its keys are on the world's page. A new key is shown once, so copy it then.
- An
ow_r_key reads, with no PIN:Client("ow_r_…"). - An
ow_w_key reads and writes. Writes also send a PIN:Client("ow_w_…", "…"). - For code that writes, give it its own agent seat, a key for one tool or script. The seat's key and its
ow_s_secret (passed as the PIN) work in one world, and you can remove them without touching your account PIN.
Elements are plain dicts:
writer = Client("ow_w_…", "ow_s_…")
peak = writer.create("location", {"name": "Dragon Peak"})
writer.patch("location", peak["id"], {"supertype": "Mountain"})
for character in client.iter_elements("character", expand=["species"]):
print(character["name"])
World folders
A world folder is one JSON file per element, in the world folder format. Atlas, the OnlyWorlds editor, reads and writes the same format.
from onlyworlds import export_world, read_folder
export_world("moppetopia", api_key="0000000001")
folder = read_folder("moppetopia")
print(folder.world["name"], len(folder.elements), "elements")
An exported folder's world.json says where it came from and which part of the world it holds: source names the server, the world's id and when it was exported, and exported_as says whether the key read the whole world as its owner, as a member (with its role), or as one Character (a guest's slice). When the server can't say which (GET /me refused or missing), exported_as is left out rather than guessed.
write_folder(root, world, elements) writes a folder from a world dict and a dict of element lists by type, keeping every key the world dict has. Reading never changes a file, and a file without an id is skipped and left alone.
To send a folder's edits back, keep a copy exported before you edited, then push the difference:
from onlyworlds import push
result = push("moppetopia-before", "moppetopia", api_key="ow_w_…", pin="ow_s_…", log_path="push.jsonl")
push sends only the fields that changed, retries what the server asks it to retry, checks that what came back is what it sent, and logs every write, so running it again skips what already landed. dry_run=True shows the plan without sending it.
Export to OKF
A world folder can be exported as a valid Open Knowledge Format (OKF) 0.2 bundle, as the spec stands at one pinned commit (onlyworlds.okf.OKF_COMMIT): one markdown file per element, with its fields in the frontmatter, its text under headings and its links (in-prose ow:// references included) as markdown links, plus an index per type. This needs PyYAML, which the rest of the package does not:
pip install "onlyworlds[okf]"
python -m onlyworlds.okf moppetopia moppetopia-okf
from onlyworlds.okf import export_okf
report = export_okf("moppetopia", "moppetopia-okf")
print(report.elements, "elements,", len(report.notes), "notes")
The output directory must not exist yet. Anything the export leaves out (a file that isn't an element, a key that isn't a field, a link to the wrong type) is listed in report.notes; the command prints these on stderr. The same folder always gives the same bytes. What a bundle never carries: the server's bookkeeping (change_seq, created_at, who wrote what) and the world's key, so a bundle is a copy to read elsewhere, not a folder to sync.
The doctor
The doctor checks a world and reports what its keeper should look at, each finding with the element and the reason. It only reads: it never changes a file or sends a write.
python -m onlyworlds.doctor moppetopia # a world folder
OW_API_KEY=ow_r_… python -m onlyworlds.doctor --api # the key's world (OW_API_PIN when the key needs one)
from onlyworlds import read_folder
from onlyworlds.doctor import doctor_folder
report = doctor_folder(read_folder("moppetopia"))
for finding in report.findings:
print(finding["flag"], finding["element"], finding["reason"])
It looks for links to elements that are not there, Events that end before they start, elements with no links in or out, two elements of one type whose names collide once case, character width, spacing and punctuation are folded (a name shared across types, such as a Location and its Pin, is not one), stubs never filled (a known-of element older than --days, or an element with no text in any of its type's text fields, on every type but map, marker, pin and relation), and a charter Law untouched after --changes changes in the feed. On a folder it also checks world.json: a format_version older than this package's, and an api_key in a world.json that git tracks. Each check says how much it could compare, and one it cannot run (a folder has no change feed; nothing records whether a message was read) is listed with the reason, never counted as clean. A finding on a message or a room thing (supertype Message, Board, Work item, Spec, Heart, Vote or Sitting) is marked "kind": "message" and listed last, outside the count of things to look at. --json prints the same report for a tool, and --packet prints the findings as work packets (the packet extension profile: a Relation with supertype Work item, its elements with a reason each under x_packet_elements, a JSON array, empty when clean) for a tool that lands them in a world or a parlor. The exit code is 0 when there is nothing to look at, 1 when there is (a message finding too), 2 when nothing could be read.
The API door reads the key from OW_API_KEY and the PIN from OW_API_PIN, or from the toolkit's ONLYWORLDS_API_KEY and ONLYWORLDS_API_PIN when those are not set.
Errors
A failed call raises ApiError with the API's code, param (the field that failed) and doc_url, and flags for the cases you handle differently: is_validation_error, is_auth_error, is_not_author, is_owner_only, is_id_conflict, is_resync_required, is_busy, is_precondition_failed.
To write only if nobody changed an element since you read it, pass its change_seq: client.patch("character", el["id"], {"name": "Mine"}, if_match=el["change_seq"]) (on edit_links too). If it moved, nothing is written and PreconditionFailed is raised, whose current is the element as it is now. The server never merges: compare the copy you edited from with current, and send again with if_match=current["change_seq"] or ask which version to keep.
The change feed
walk = client.walk_changes(saved_cursor) # None for everything
for op in walk:
... # op["op"] is "upsert" or "delete"
saved_cursor = walk.cursor # opaque: keep it, don't parse it
Schema
The 22 element types and each field's kind are generated from the published schema distribution; onlyworlds.SCHEMA_VERSION and onlyworlds.SCHEMA_DIST_TAG say which one. onlyworlds.PUBLIC_FIELDS names, per type, the fields OnlyWorlds shows on a known-of element (one a guest sees only one step out) beside its name and description. The package's own version doesn't track the schema's.
Not yet: typed element models, the account routes, a snapshot writer.
Develop
uv sync
uv run pytest -q
uv run ruff check . && uv run mypy --strict src
uv run python codegen/generate_schema.py --check
Three groups of tests run only when asked:
OW_CONFORMANCE_FIXTURE=<path>: the folder-format conformance fixture from the Atlas repo.OW_OPENAPI=https://www.onlyworlds.com/api/v2/openapi.json(or a saved copy): checks the client's routes, parameters and fields against the API's own OpenAPI document.OW_LIVE=1: read-only calls against the demo world.
OKF_REFERENCE_SRC=<path> (the src/ of an open-knowledge-format checkout) also runs OKF's own parser and viewer over an exported bundle.
License
MIT. See LICENSE.
Metadata
Release files for onlyworlds 0.12.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| onlyworlds-0.12.0.tar.gz | 199.6 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| onlyworlds-0.12.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 266.3 kB
Release files / onlyworlds-0.12.0.tar.gz
| Download URL | onlyworlds-0.12.0.tar.gz |
|---|---|
| Size | 199.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
748bb361aa1bc04a036696cdca2bde49bc0974afbf677fafc653d70427984d1a
|
|
BLAKE2b-256 checksum How to use checksums |
c86cc9c3bb692fc325b9c1534772529afc2b7b89f209a7b974db7be3203c81d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / onlyworlds-0.12.0-py3-none-any.whl
| Download URL | onlyworlds-0.12.0-py3-none-any.whl |
|---|---|
| Size | 66.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d5736bdebfec27ec0fee66eb870401c8d524fc2708521cb50799cf9732441da8
|
|
BLAKE2b-256 checksum How to use checksums |
43a675fd0c13f42aa79ab3d249084957f99467eacb5f5816369acc1154c3d57a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for onlyworlds 0.12.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for onlyworlds-0.12.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.