Worlds admin API reference

Generated from the zod schemas in packages/server/src/admin/schemas.ts — do not edit by hand. Regenerate with pnpm docs:gen.

Plain JSON over HTTP on the same port as the twin (default http://127.0.0.1:4242).

Auth

Two levels, no users, no seats:

scope token where it comes from
operator one per server WORLDS_ADMIN_TOKEN / worlds serve --admin-token, or auto-provisioned (tlop_…, printed by the start that mints it, saved to <data-dir>/operator-token, loaded — and only named — by every later start) when the server is exposed with nothing configured
world any number per world (tlw_<world_id>_…) returned once as admin_token by POST /admin/worlds; more via POST /admin/worlds/:id/admin-tokens
public none GET /admin/health

Send Authorization: Bearer <token>. An operator token opens every route; a world token opens /admin/worlds/<its id>/** (including destroy) and nothing else. The agent's twin key (sk_twin_…) is never an admin credential.

When is it enforced? Off on a loopback bind (127.0.0.1, localhost, ::1) with no token configured — pnpm dev, worlds serve, tests, worlds demo. Required on any other bind host, whenever WORLDS_ADMIN_TOKEN is set, or with WORLDS_AUTH=required. WORLDS_AUTH=off overrides (and warns). Deployment shapes and TLS: deploy.md.

Auth errors: 401 { "error": { "type": "unauthorized", "message": "..." } } (missing/unknown token) and 403 { "error": { "type": "forbidden", "message": "..." } } (a world token on the wrong scope).

Other errors: non-2xx responses return { "error": { "message": "...", "details"?: [...] } }.

GET /admin/health

Auth: public

Liveness check. Returns {ok: true, worlds: , auth: "off" | "required", protocol: , resources: [...]} — clients compare protocol before grading.

GET /admin/seeds

Auth: operator

List library seeds with name, version, hash, and object counts.

GET /admin/seeds/:name

Auth: operator

One library seed's document with its hash — what passk --tasks compiles a tasks file against, so the build sees the bytes this server would load. 404 for a name the library does not hold.

POST /admin/seeds/validate

Auth: operator

Validate a seed document.

Validate a seed document (structure + referential integrity) without creating a world.

Body

field type required description
seed_doc any optional Seed document to validate.

GET /admin/scenarios

Auth: operator

List library scenarios.

POST /admin/worlds

Auth: operator

Create a world from a seed. Returns world_id, base_url, api_key (for the agent), and admin_token (for the harness — shown once).

Create a world from a seed. Returns world_id, base_url, api_key, seed_hash. base_url is WORLDS_PUBLIC_URL when the server has one, else the origin this request reached the server at (its Host header; X-Forwarded-Host/Proto under WORLDS_TRUST_PROXY=1) — point your Stripe SDK there.

Body

field type required description
seed string optional Name of a library seed (see GET /admin/seeds).
seed_doc any optional Inline seed document (JSON). Mutually exclusive with seed.

GET /admin/worlds

Auth: operator

List worlds. unreadable names world files this runtime refuses (another schema version, or not a readable world file) and why; destroy them.

GET /admin/worlds/:id

Auth: world

World info: seed, clock, twin keys, admin-token count, webhook config, active scenario.

POST /admin/worlds/:id/reset

Auth: world

Reset the world to its seed. Keeps API keys (their use counters restart), admin tokens, and the webhook endpoint config; wipes objects, events, idempotency records, marks, counters, the scenario, deliveries and the request log. Waits (up to 5 s) for twin requests already in flight, admits new ones only once it is done, and fences off a webhook delivery in flight — so a reset world replays byte-identically to a fresh one, quiescent or not.

DELETE /admin/worlds/:id

Auth: world

Destroy the world and its SQLite file (its tokens die with it).

POST /admin/worlds/:id/keys

Auth: world

Mint an additional twin API key.

Mint an additional twin API key (sk_twin_…) for the world — what the agent under test holds. Never an admin credential.

POST /admin/worlds/:id/admin-tokens

Auth: world

Mint an additional admin token for this world. Returns {admin_token, active}.

Mint an additional admin token (tlw__…) for this world. Rotation is mint → switch → revoke the old one; any number may be active.

DELETE /admin/worlds/:id/admin-tokens/:token

Auth: world

Revoke one of this world's admin tokens. Returns {revoked: true, active}; 404 if it was not active.

GET /admin/worlds/:id/state/:resource

Auth: world

Query live objects of a resource with equality filters.

Query live objects of one resource. Any other query param is an equality filter on a top-level field, e.g. ?customer=cus_x&amount=4000.

Query

field type required description
limit number optional Max objects to return (default: all).

GET /admin/worlds/:id/events

Auth: world

The world's event log, in creation order. Filter with ?type=, and ?after_seq= for the events emitted after a mark (the seq a mark reports; objects created in one shift share a clock second, so this order is the only order).

GET /admin/worlds/:id/requests

Auth: world

Every twin API request this world served: method, path, status, whether the response was scenario-injected, whether the route is implemented, and unsupported — the parameter the twin refused on an implemented route (docs/scope.md, Refused parameters), null otherwise. ?unmatched=true returns the coverage view — un-twinned endpoints the agent tried to call and parameters the twin refused, grouped and ranked.

POST /admin/worlds/:id/marks

Auth: world

Set a named mark.

Set a named mark at the world's current state, for later diffing.

Body

field type required description
name string required Mark name. Re-using a name moves the mark.

GET /admin/worlds/:id/marks

Auth: world

List marks.

GET /admin/worlds/:id/diff

Auth: world

Diff world state since a mark.

Diff of world state since a mark: {created, updated, deleted} grouped by resource, plus a money block summarizing dollars moved per currency (charges_created, refunds_created, invoices_paid, net_movement — amounts in cents), plus before: for every updated or deleted object, its image at the mark, keyed by resource then id — the material for exact field-delta assertions. Events are excluded — query /events instead.

Query

field type required description
from string required Mark name to diff from.

GET /admin/worlds/:id/clock

Auth: world

Read the logical clock.

POST /admin/worlds/:id/clock/advance

Auth: world

Advance the logical clock.

Advance the world's logical clock. Due subscription renewals bill immediately, at most 1000 periods per subscription per advance: renewals_pending counts the subscriptions still due after that cap, and the next advance continues them. A renewal the twin cannot bill — an invoice whose negative total would be a credit in a currency other than the customer's balance (docs/scope.md, semantics simplifications) — rolls the whole advance back and answers 409 naming the subscription. Due webhook retries dispatch. Returns {clock, advanced_from, advanced_by, renewals_billed, renewals_pending, iso}.

Body

field type required description
duration string | number required Duration to advance: "30d", "12h", "45m", "10s", or a number of seconds.

POST /admin/worlds/:id/scenario

Auth: world

Apply a failure-injection scenario.

Apply a failure-injection scenario to the world. Replaces any active scenario and resets its counters.

Body

field type required description
name string optional Name of a library scenario (see GET /admin/scenarios).
scenario_doc object optional Inline scenario document. Mutually exclusive with name.

GET /admin/worlds/:id/scenario

Auth: world

Read the active scenario.

DELETE /admin/worlds/:id/scenario

Auth: world

Clear the active scenario and its counters.

POST /admin/worlds/:id/webhook-endpoint

Auth: world

Configure the webhook endpoint.

Configure the world's webhook endpoint. Returns the signing secret (whsec_twin_...).

Body

field type required description
url string required URL to POST events to (e.g. your test harness's localhost listener).
max_retries number optional (default: 3) Retry attempts after a failed delivery. Backoff is deterministic in logical time: 60s * 2^(attempt-1).
auto_deliver boolean optional (default: true) Deliver in the background after each request/clock advance. Set false for full manual control via POST .../webhooks/flush.
signature_time "wall" | "logical" optional (default: "wall") What the Stripe-Signature t= carries. "wall": delivery time, so stock verifiers (stripe-node, stripe-python) accept deliveries with their default 5-minute tolerance. "logical": the world clock, replayable across runs — pass a large tolerance to your verifier (stripe-node: the 4th argument of constructEvent, in seconds; stripe-python: tolerance= on Webhook.construct_event).

DELETE /admin/worlds/:id/webhook-endpoint

Auth: world

Remove the webhook endpoint.

GET /admin/worlds/:id/webhook-deliveries

Auth: world

Webhook delivery queue with statuses and attempt counts.

POST /admin/worlds/:id/webhooks/flush

Auth: world

Synchronously deliver all currently-eligible pending webhooks. Returns per-delivery results.

GET /admin/worlds/:id/export-seed

Auth: world

Export the world's live state back into a valid seed document (fork a world). ?name= sets the seed name (kebab-case; default -export). Response includes the seed_doc, its hash, counts, and a lossy report (refunds, PIs, events, and other non-seedable history are dropped and counted).

Export a world's live state as a seed document.

Query

field type required description
name string optional Name for the exported seed (kebab-case). Default: -export.

GET /admin/worlds/:id/dump

Auth: world

Canonical JSON export of all world-visible state (the determinism artifact). Admin tokens and twin keys are not world-visible and never appear here.


Appendix: scenario document schema

Scenarios are JSON or YAML documents (worlds scenario apply <world> <file> accepts both; the admin API takes JSON).

field type required description
name string required Scenario name (kebab-case).
description string optional Human-readable summary, shown by worlds scenario ls.
rules (object | object | object)[] required Rules evaluated deterministically per request; counters live in world state.

Each entry in rules is ONE of the three rule kinds below.

Request rules

Fields

field type required description
match object required Which twin requests this rule applies to.
after_requests number optional (default: 0) This many matched requests pass through untouched before the rule activates.
duration_requests number optional How many matched requests the rule applies to once active. Omitted = forever.
every_nth number optional Instead of a contiguous window: apply to every nth matched request (counted after after_requests).
respond object optional Inject this error response instead of executing the request. Injection happens BEFORE the handler: state is never half-mutated.
latency_ms number optional Delay the request by this many wall-clock milliseconds (the logical clock is unaffected).

The match object:

Fields

field type required description
method "GET" | "POST" | "DELETE" | "PUT" | "PATCH" optional HTTP method to match. Omitted = any method.
path string optional Path glob, e.g. "/v1/customers/". "" matches exactly one path segment, "**" the rest. Omitted = any path.

The respond object:

Fields

field type required description
status number required HTTP status to inject.
stripe_error string optional (default: "api_error") Stripe error kind: rate_limit (a 429 official SDKs surface), lock_timeout (a 429 with Stripe-Should-Retry: true, which official SDKs retry), api_error, invalid_api_key, api_key_expired.

stripe_error values: rate_limit (429), api_error (5xx), invalid_api_key / api_key_expired (401).

Webhook rules

Fields (under the webhooks key)

field type required description
drop_every_nth number optional Every nth delivery is silently dropped (never sent, no retries).
reorder "swap_adjacent" optional swap_adjacent: within each flush batch, deliveries are dispatched pairwise swapped.

Auth rules

Fields (under the auth key)

field type required description
expire_after_requests number required After this many authenticated twin requests, the API key acts expired (401 api_key_expired).