Speaking overlay), paired
with a brain: the persona, lore, voice, and stance palette that decide what
the character says and how they feel.
Because a character is a world, everything you already know carries over:
tenancy, session tokens, versions, forking, and usage
all work the same way. Three things are new: how you create one, the /say
turn loop, and /tts voice.
The brain is a secret. The persona and lore are loaded server-side to drive
each turn. They are never sent to the browser. The embed only ever receives
what it needs to render: the stances, voice id, greeting, and intro.
The auth model
There are two distinct callers, exactly as with worlds:- Your backend (secret
sk_key) creates, lists, updates, and forks characters, and mints session tokens. - The browser (short-lived session token) runs the live conversation:
POST /v1/characters/{id}/sayand/tts. The secret key and the persona never reach the client.
Create a character
POST /v1/characters (secret key) supports three modes.
Import a complete character (examples)
The fastest way to get a playable character is to import one. The six shipped first-party characters are published as ready-to-import example files, each shaped{ "world": … }:
Explicit
Provide the brain and a stance palette. The server compiles the stance-graph for you. Each stance is one emotional state with anexpression (its visual prose)
and a mood (the cue the brain reads to choose it).
cover, frame_url, frame_b64, or, as
above, painted from premise when you give no image.
Authored
Give a frame (or a premise alone) andauthored: true, and the platform invents
the persona, subject, style, and a 4–6 stance palette for you:
Manage characters
Stance-node and graph edits go through the existing
/v1/worlds/{id}/… endpoints. A character’s graph is an
SMWorld.
Talk to a character
Mint a session token for the character (theworldId is the
character’s id), then call /say from the browser with that token. The brain is
loaded server-side. You send only what the player said.
turn.stance, fire the
hidden Speaking overlay, and optionally render turn.action:
change_location/fetch_object: scene prose (morph the place / hold up an object). Apply locally, no extra call.show_image: the character shows a picture. Paint it withPOST /v1/characters/{id}/image{ token, prompt: action.image_prompt }→{ src, generated }. With no prompt (or on failure / over quota) it returns the character’s base image, so a demo card is never empty. Metered asimage.
/say is metered as a say turn and /tts as a tts turn. See
Pricing.
Cross-session memory (per player)
Characters remember the people they talk to (decaying affect/rapport, a consolidated summary, and a “welcome back” line), exactly like the first-party character page. You bring your own player identity: setplayerIdentity to
your user id when you mint the session token, and memory is scoped per
(your app, character, playerIdentity). Build on top of the API with your own
user list, and each of your users gets their own relationship.
- The default identity is
anon, which is never persisted (so anonymous players don’t share one relationship). Pass a realplayerIdentityto opt in. POST /v1/characters/{id}/sayautomatically loads that player’s prior affect + summary and persists the turn (no extra calls in the happy path).POST /v1/characters/{id}/resume→{ returning, welcome, affect, messageCount }for a memory-aware greeting when they come back.POST /v1/characters/{id}/memory/endconsolidates the session into the summary (call on unmount, though the embed does this for you).GET /v1/characters/{id}/memoryinspects it.DELETEforgets it (start over / right-to-be-forgotten).
Read a player’s transcript
To display or manage the chat in your own UI, read the stored transcript from your server:GET /v1/characters/{id}/transcript is secret-key
auth (it’s your backend reading its own end-user’s history, not the embed), so pass
playerIdentity as a query param (the session token isn’t involved). Paginate with
limit and cursor (the opaque nextCursor from the previous page).
transcript is a rolling window of the most recent turns (capped at 40), not a
complete log. Older turns are folded into summary (a consolidated few-sentence
recap) rather than kept verbatim, and anonymous players (playerIdentity = anon)
are never persisted, so they have no transcript. If you need a permanent, uncapped
archive of every message, store turns on your side as you send them.Embed a live conversation
A character embeds exactly like a world: mint a session token and load the embed with it. The streaming video, stance morphs, and the talk loop run inside the iframe. The persona/lore stay server-side. See Embedding.Example characters
Six hand-authored first-party characters ship as importable examples. Drop any one intoPOST /v1/characters (import mode) to get your own playable copy.
Each file is the exact compiled character (its stance-graph, persona, lore, and
voice), so the imported copy is faithful to the shipped one.

