> ## 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.

# Character consistency

> Define a character once and carry it — identical face, build, hair and clothing — across every scene, level and book chapter.

Multi-scene games need the **same character** to show up in scene after scene. The
world model carries a character's identity in **pixels, not prose**: if the
character appears in a scene's seed image, the model holds them steady. So the way
to keep a protagonist consistent across levels is to generate each new scene's seed
**from a reference image of that character**, not from prose alone.

*Character coherence* is the same idea under a different name — this guide uses
"character consistency" and "character coherence" interchangeably.

This is exactly what the hand-authored worlds do by hand — the Stormcaller
tutorial→fight seed reuses the same knight image so the character doesn't change
between scenes. The `subject_ref` + `/scenes` API automates it, and the same
mechanism threads through [book-to-game](/world-generation-examples) chapters.

<Note>
  `subject_ref` is the **pixel** counterpart of a world's prose `subject` lock.
  Canonical references work best as rear or three-quarter views — the rear-chase
  camera hides faces, so identity rides on hair, build and clothing.
</Note>

## Define the character once

Create a world with a `subject_ref` — either an inline image (`image_b64`) or an
already-hosted `image_url`, plus an optional prose `descriptor`. The opening seed
is painted **from** the character, and the reference is stored on the world as
`subjectRef { imageUrl, descriptor }` for every later scene to reuse.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.alakazam.gg/v1/worlds' \
    -H 'Authorization: Bearer sk_live_…' \
    -H 'Content-Type: application/json' \
    -d '{
      "premise": "The ranger tracks a wounded beast through snowbound pines.",
      "subject_ref": {
        "image_url": "https://cdn.example.com/hero.png",
        "descriptor": "a fur-cloaked ranger with a longbow"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch("https://api.alakazam.gg/v1/worlds", {
    method: "POST",
    headers: {
      Authorization: "Bearer sk_live_…",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      premise: "The ranger tracks a wounded beast through snowbound pines.",
      subject_ref: {
        image_url: "https://cdn.example.com/hero.png",
        descriptor: "a fur-cloaked ranger with a longbow",
      },
    }),
  });
  const { worldId } = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
      "https://api.alakazam.gg/v1/worlds",
      headers={
          "Authorization": "Bearer sk_live_…",
          "Content-Type": "application/json",
      },
      json={
          "premise": "The ranger tracks a wounded beast through snowbound pines.",
          "subject_ref": {
              "image_url": "https://cdn.example.com/hero.png",
              "descriptor": "a fur-cloaked ranger with a longbow",
          },
      },
  )
  world_id = res.json()["worldId"]
  ```
</CodeGroup>

An `image_b64` is hosted to a durable URL before the world is stored, so no inline
blob is ever persisted. `image_url` must be an **already-hosted** URL: a `data:`
URI is rejected `400` (host it first, or send the bytes as `image_b64`), and if a
supplied `image_b64` cannot be hosted the request **fails closed** with `502` — no
world is created rather than one that silently dropped the reference you paid for.

## Pin a specific seed frame

`subject_ref` locks *who* is in the scene; `frame_url` locks *what the opening
frame is*. Pass an already-hosted image as `frame_url` (or inline bytes as
`frame_b64`) on `POST /v1/worlds` and the world **boots from that exact frame**
instead of one the model paints from the premise. Combine the two — a pinned
opening frame plus a `subject_ref` — and the character rides in coherent from the
very first frame and stays that way scene-to-scene. This is the trick the
hand-authored **Stormcaller** world pulls off by hand: a deliberately chosen
entrance frame with the same knight, reused so nothing drifts.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.alakazam.gg/v1/worlds' \
    -H 'Authorization: Bearer sk_live_…' \
    -H 'Content-Type: application/json' \
    -d '{
      "premise": "The ranger tracks a wounded beast through snowbound pines.",
      "frame_url": "https://cdn.example.com/opening-frame.png",
      "subject_ref": {
        "image_url": "https://cdn.example.com/hero.png",
        "descriptor": "a fur-cloaked ranger with a longbow"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch("https://api.alakazam.gg/v1/worlds", {
    method: "POST",
    headers: {
      Authorization: "Bearer sk_live_…",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      premise: "The ranger tracks a wounded beast through snowbound pines.",
      frame_url: "https://cdn.example.com/opening-frame.png",
      subject_ref: {
        image_url: "https://cdn.example.com/hero.png",
        descriptor: "a fur-cloaked ranger with a longbow",
      },
    }),
  });
  const { worldId } = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
      "https://api.alakazam.gg/v1/worlds",
      headers={
          "Authorization": "Bearer sk_live_…",
          "Content-Type": "application/json",
      },
      json={
          "premise": "The ranger tracks a wounded beast through snowbound pines.",
          "frame_url": "https://cdn.example.com/opening-frame.png",
          "subject_ref": {
              "image_url": "https://cdn.example.com/hero.png",
              "descriptor": "a fur-cloaked ranger with a longbow",
          },
      },
  )
  world_id = res.json()["worldId"]
  ```
</CodeGroup>

Pass `frame_url` as an **already-hosted**, fetchable image URL — it becomes the
world's opening seed, so the model must be able to load it. To send raw bytes
instead, use `frame_b64`.

<Note>
  Don't have a frame yet? [`POST /v1/seed-frame`](/vision) paints one from a prompt and
  returns a hosted `url` you can hand straight back as `frame_url` — the supported
  way to author a deliberate opening frame without leaving the API.
</Note>

## Generate the next scene with the same character

`POST /v1/worlds/{id}/scenes` reads the stored `subjectRef` and paints a new seed
**from** it plus your scene `prose`. The result is a hosted image; with `pin:true`
it also becomes the world's entrance seed (a versioned `set_entrance` write that
keeps the current entrance state).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.alakazam.gg/v1/worlds/WORLD_ID/scenes' \
    -H 'Authorization: Bearer sk_live_…' \
    -H 'Content-Type: application/json' \
    -d '{
      "prose": "The same ranger now stands in a torchlit cave mouth, bow drawn.",
      "pin": false
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://api.alakazam.gg/v1/worlds/WORLD_ID/scenes",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_live_…",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        prose: "The same ranger now stands in a torchlit cave mouth, bow drawn.",
        pin: false,
      }),
    },
  );
  const { image_url, pinned } = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
      "https://api.alakazam.gg/v1/worlds/WORLD_ID/scenes",
      headers={
          "Authorization": "Bearer sk_live_…",
          "Content-Type": "application/json",
      },
      json={
          "prose": "The same ranger now stands in a torchlit cave mouth, bow drawn.",
          "pin": False,
      },
  )
  scene = res.json()
  print(scene["image_url"], scene["pinned"])
  ```
</CodeGroup>

The scene prose is the "new scene" description; the character rides in from the
reference pixels. The `descriptor` reinforces identity in words and **defaults to
the world's `subject`** when the reference has none. `/scenes` is metered `image`
(reserve-before-spend, fail-closed); a pin failure still returns the generated
image so a paid render is never lost. A world with no stored `subject_ref` returns
`404` — attach one first (below).

## Attach or rotate a character later

Already have a world without a character, or want to swap it? Use the
[`set_subject_ref`](/graph-editing#attach-a-character-set-subject-ref) batch op —
metadata-only, no re-generation:

```bash theme={null}
curl -X POST 'https://api.alakazam.gg/v1/worlds/WORLD_ID/ops' \
  -H 'Authorization: Bearer sk_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "ops": [
      { "op": "set_subject_ref",
        "imageUrl": "https://cdn.example.com/hero.png",
        "descriptor": "a fur-cloaked ranger, hood down" }
    ]
  }'
```

After the attach, `POST /v1/worlds/{id}/scenes` works on that world. The create-time
`subject_ref` field and the `set_subject_ref` op are the two write surfaces for the
reference, and they agree: both take a hosted `imageUrl`/`image_url` and both reject
`data:` URIs.

## Status codes

| Status | Meaning                                                                                                                                                      |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `200`  | Scene generated (and pinned when `pin:true`). A graceful pin fallback returns the image with `pinned:false` and a `detail`.                                  |
| `400`  | On create: an invalid `subject_ref` (a `data:` URI in `image_url`). On `/scenes`: missing `prose`.                                                           |
| `402`  | Daily `image` quota exceeded.                                                                                                                                |
| `404`  | The world has no stored `subject_ref` to paint from (attach one first).                                                                                      |
| `409`  | A concurrent modification moved the world's `rev` during a pin. The image is still returned; reload and retry the pin.                                       |
| `502`  | On create: a supplied `image_b64` could not be hosted (fail closed — no world created). On `/scenes`: the paint/host upstream failed (reservation refunded). |
