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

# Session handoff & continuity

> Keep a live world-model stream clean across long sessions with the drift-refresh double-buffer — mint a fresh runtime token, warm a second session, crossfade, and drop the old one.

A world-model stream **drifts**: the longer a single runtime session runs, the
further the image wanders from where it started. Rather than let the picture
degrade, you start a **second session and switch to it** — the same pattern the
first-party character and world embeds use under the hood. This guide is the
manual version of that handoff.

The trick is that the *logical* session (your quota slot, the world, the player)
is decoupled from the *runtime* stream you're currently rendering. So you can
swap the stream without ending the session.

<Note>
  This is the **same logical session** throughout — no extra concurrency slot is
  taken. Each refresh reserves a fresh cycle of session-minutes (metered
  fail-closed), so a handoff costs a new minute-cycle, not a new session.
</Note>

## The double-buffer

<Steps>
  <Step title="Mint & connect — session A">
    Mint a session token on your server
    ([`POST /v1/sessions/token`](/studio-worlds)) and connect it in the browser
    (`POST /v1/sessions/connect`). This is your visible stream, **session A**.
  </Step>

  <Step title="When A drifts, refresh for a fresh runtime token">
    Call `POST /v1/sessions/refresh` with the **same session token**. It mints a
    brand-new runtime connect token for the same logical session:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST 'https://api.alakazam.gg/v1/sessions/refresh' \
        -H 'Content-Type: application/json' \
        -d '{ "token": "SESSION_TOKEN" }'
      ```

      ```javascript JavaScript theme={null}
      const r = await fetch("https://api.alakazam.gg/v1/sessions/refresh", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ token: sessionToken }), // the SAME session token
      });
      const { reactorJwt, expiresIn, expiresAt } = await r.json();
      ```
    </CodeGroup>

    The response is `{ reactorJwt, expiresIn, expiresAt }` — a fresh runtime
    token, nothing else.
  </Step>

  <Step title="Boot a warm session B, seeded from A's last frame">
    Open a **hidden** second player with the fresh `reactorJwt`, seeding it from
    the last frame of session A so the two streams line up. Let it warm for a
    couple of seconds until it's streaming cleanly.
  </Step>

  <Step title="Crossfade, then drop A">
    At the next idle gap (never mid-action) crossfade the visible layer from A to
    B, then unmount A. B is now your live stream — repeat from step 2 the next
    time it drifts.
  </Step>
</Steps>

## Metering & status codes

Each refresh reserves one fresh cycle of `session_seconds` against your daily
budget before it mints the token, so continuity is metered honestly.

| Status | Meaning                                                                                                             |
| ------ | ------------------------------------------------------------------------------------------------------------------- |
| `200`  | A fresh runtime token: `{ reactorJwt, expiresIn, expiresAt }`.                                                      |
| `401`  | The session token is invalid or expired.                                                                            |
| `402`  | Your daily session-minute budget is exhausted — stop re-seeding and keep the current stream until tomorrow's reset. |
| `403`  | The session already ended (settled via `/v1/sessions/end`) or was revoked — a stale token can't mint a new stream.  |
| `503`  | Runtime not configured.                                                                                             |

<Note>
  On a `402`, don't tear down the visible stream — the drifted-but-live picture
  is better than a dead one. Just stop refreshing until the budget resets.
</Note>

## Works for worlds and characters

`/v1/sessions/refresh` is **world-agnostic**: it authorizes by the play token's
app + session id and mints a fresh runtime token regardless of what's playing.
The same handoff powers the first-party **character** embeds (which re-seed to
hold a mood without a mid-speech cut) and **world** embeds alike, so you can use
this pattern for either.

## Next steps

<CardGroup cols={2}>
  <Card title="Run a Studio world" icon="gamepad-2" href="/studio-worlds">
    Play a world you built visually through the session flow.
  </Card>

  <Card title="Embedding" icon="square-code" href="/embedding">
    Theming, events, and the built-in token refresh callback.
  </Card>
</CardGroup>
