> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alakazam.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Characters

> Create talk-to-able AI characters — a stance-graph world plus a brain (persona, lore, voice) — and embed a live conversation.

A **character** is a special kind of world you *talk to*. Under the hood it's a
[SMWorld](/concepts) **subtype**: the same stance-graph that drives the live video
(each node is an emotional *stance*, plus one hidden `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](/embedding), [versions](/versions), forking, and usage
all work the same way. Three things are new: how you **create** one, the **`/say`**
turn loop, and **`/tts`** voice.

<Note>
  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.
</Note>

## 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}/say` and `/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 four shipped
first-party characters are published as ready-to-import example files
(`conjure-service/examples/characters/*.json`), each shaped `{ "world": … }`:

```bash theme={null}
curl -X POST https://api.alakazam.gg/v1/characters \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  --data @examples/characters/nero.json
# → { "id": "…", "slug": "…", "kind": "character", "cover": "https://…" }
```

This instantiates **your own** copy — you own it, can fork it, version it, and
talk to it. See [Example characters](#example-characters) below.

### Explicit

Provide the brain and a stance palette; the server compiles the stance-graph for
you. Each stance is one emotional state with an `expression` (its visual prose)
and a `mood` (the cue the brain reads to choose it).

```bash theme={null}
curl -X POST https://api.alakazam.gg/v1/characters \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Aria",
    "premise": "a young astronomer in a cluttered observatory at night",
    "subject": "a young woman with ink-stained fingers and tired, bright eyes",
    "style": "warm lamplight, 35mm film grain",
    "persona": "Curious, dry-witted, a little lonely. Lights up about the stars.",
    "greeting": "Oh — you'\''re up late too?",
    "voiceId": "<elevenlabs-voice-id>",
    "stances": [
      { "id": "neutral",   "label": "Neutral",   "mood": "her default",       "expression": "a calm, attentive look" },
      { "id": "delighted", "label": "Delighted", "mood": "when praised",      "expression": "eyes lighting up, a quick grin" }
    ]
  }'
```

The cover/seed frame is resolved from `cover`, `frame_url`, `frame_b64`, or — as
above — **painted from `premise`** when you give no image.

### Authored

Give a frame (or just a premise) and `authored: true`, and the platform invents
the persona, subject, style, and a 4–6 stance palette for you:

```bash theme={null}
curl -X POST https://api.alakazam.gg/v1/characters \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "authored": true, "premise": "a grizzled lighthouse keeper who has seen things" }'
```

All three modes reserve **one generation** against your daily quota and run the
same fail-closed validation gate before persisting.

## Manage characters

| Method   | Path                        | What                                                                                     |
| -------- | --------------------------- | ---------------------------------------------------------------------------------------- |
| `GET`    | `/v1/characters`            | List your characters (cursor-paginated).                                                 |
| `GET`    | `/v1/characters/{id}`       | Read one — metadata **and** the brain (you own it).                                      |
| `PATCH`  | `/v1/characters/{id}`       | Update metadata + brain fields (persona, greeting, lore, voiceId, stances, intro, free). |
| `DELETE` | `/v1/characters/{id}`       | Soft-delete.                                                                             |
| `POST`   | `/v1/characters/{id}/forks` | Clone (world + brain) into a new character.                                              |

Stance-node and graph edits go through the existing
[`/v1/worlds/{id}/…`](/graph-editing) endpoints — a character's graph *is* an
SMWorld.

## Talk to a character

Mint a [session token](/embedding) for the character (the `worldId` 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.

```javascript theme={null}
const r = await fetch(`https://api.alakazam.gg/v1/characters/${id}/say`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    token: SESSION_TOKEN,
    text: "what are you working on?",
    currentStance: "neutral",
    history: [{ role: "user", text: "hi" }, { role: "character", text: "Oh — hello." }],
  }),
});
const turn = await r.json();
// → { reply, stance, emotion, affect, action }
```

The response drives the live scene: move the video to `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 with
  `POST /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 as `image`.

To voice the line:

```javascript theme={null}
const audio = await fetch(`https://api.alakazam.gg/v1/characters/${id}/tts`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token: SESSION_TOKEN, text: turn.reply }),
});
new Audio(URL.createObjectURL(await audio.blob())).play();
```

`/say` is metered as a **`say`** turn and `/tts` as a **`tts`** turn — see
[Pricing](/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:** set `playerIdentity` 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.

```bash theme={null}
# mint a token FOR a specific player
curl -X POST https://api.alakazam.gg/v1/sessions/token \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{"worldId":"<characterId>","playerIdentity":"your-user-id-123"}'
```

* The default identity is `anon`, which is **never persisted** (so anonymous
  players don't share one relationship). Pass a real `playerIdentity` to opt in.
* `POST /v1/characters/{id}/say` automatically 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/end` consolidates the session into the summary
  (call on unmount — the embed does this for you).
* `GET /v1/characters/{id}/memory` inspects it; `DELETE` forgets it (start over /
  right-to-be-forgotten).

All of these are **session-token** auth (the token carries the player), so they're
safe to call from the browser; the persona/lore never leave the server.

## 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](/embedding).

## Example characters

Six hand-authored first-party characters ship as importable examples — drop any
one into `POST /v1/characters` (import mode) to get your own playable copy.

| Slug      | Name    | Voice | Notes                                         |
| --------- | ------- | ----- | --------------------------------------------- |
| `cafe`    | Mina    | ✓     | A quiet afternoon; inline persona (no bible). |
| `nero`    | Nero    | ✓     | `eleven_v3`; rich character bible.            |
| `hugo`    | Hugo    | ✓     | `eleven_v3`; the public free-demo character.  |
| `gojo`    | Gojo    | ✓     | Full character bible.                         |
| `makima`  | Makima  | ✓     | Full character bible.                         |
| `astolfo` | Astolfo | ✓     | Full character bible.                         |

Each file is the exact compiled character — its stance-graph, persona, lore, and
voice — so the imported copy is faithful to the shipped one.
