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

# Testing

> The test-mode sandbox — deterministic mocks, zero GPU spend, isolated data, never billed.

Every API key is either **test** or **live**. A **test** key runs against a
sandbox: the generative endpoints return deterministic, canned responses instead
of calling a real GPU or vendor backend. Use it to build and test your
integration end-to-end — wiring, error handling, request/response shapes — with
**zero cost and zero real generation**, then swap in a live key to ship.

## Test vs live keys

Keys come in two modes, chosen when you mint the key (`mode` on
`POST /v1/apps/{id}/keys`, default `test`). The **prefix tells you which**:

| Prefix                    | Mode | Behaviour                                           |
| ------------------------- | ---- | --------------------------------------------------- |
| `sk_test_…` / `pk_test_…` | test | Sandbox — mocked responses, no spend, isolated data |
| `sk_live_…` / `pk_live_…` | live | Real generation, real runtime, metered              |

A key's mode is fixed at creation. To go from sandbox to production, mint a
`live` key — you don't "flip" a test key.

## What a test key returns

The generative endpoints below short-circuit before any backend call and return
a shape-faithful mock carrying a `"mock": true` marker. Responses are otherwise
**deterministic** — a 1×1 placeholder image, canned text — with one exception: a
newly created world/character still gets a **real, freshly-generated `id`** (and,
being private, **no slug**).

| Endpoint                                                                         | Test-mode response                                                                                                                   |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `POST /v1/worlds` (generate)                                                     | `{ worldId: <real UUID>, ok: true, cover: <1×1 PNG>, mock: true }` — private, so no `slug`                                           |
| `POST /v1/characters`                                                            | `{ id: <real UUID>, kind: "character", cover: <1×1 PNG>, mock: true }` — no `slug`                                                   |
| `POST /v1/characters/{id}/say`                                                   | `{ reply: "This is a canned test reply…", stance: "neutral", affect: {…}, mock: true }`                                              |
| `POST /v1/characters/{id}/image`                                                 | `{ src: <1×1 PNG data-URI>, generated: true, mock: true }`                                                                           |
| `POST /v1/characters/{id}/resume`                                                | `{ returning: false, welcome: "", affect: {…}, messageCount: 0, mock: true }` — no memory/LLM call                                   |
| `POST /v1/characters/{id}/memory/end`                                            | `{ ok: true, consolidated: false, mock: true }` — no consolidation call                                                              |
| `POST /v1/worlds/{id}/edit`                                                      | `{ world: <canned SMWorld>, diagnostics: [], reply: "…", mock: true }`                                                               |
| `POST /v1/seed-frame`, `/probe`, `/perceive`, `/ground/scene`, `/ground/objects` | Canned vision fixtures, each with `mock: true`                                                                                       |
| `POST /v1/sessions/connect`                                                      | Fake runtime token + a canned world: `{ worldId, reactorJwt, expiresIn, expiresAt, world, mock: true }` — no real Reactor connection |
| `POST /v1/sessions/refresh`                                                      | Fake runtime token only: `{ reactorJwt, expiresIn, expiresAt: null, mock: true }` (no `world`)                                       |

<Note>
  The `"mock": true` field is the reliable way to detect at runtime that you're
  on a test key. **Exception:** `POST /v1/characters/{id}/tts` streams a tiny
  silent `audio/mpeg` clip (real bytes, so it can flow through your audio path) —
  being binary, it carries **no** `mock` marker in the body. Detect test mode
  from the key prefix there.
</Note>

`POST /v1/worlds` with a compiled `world` body (**import**) is *not* mocked —
there's no GPU to skip. The world is validated and stored verbatim, just
namespaced into your test data (see below).

## Isolation

Test resources live in a separate namespace from live ones:

* Test worlds and characters are stored `mode='test'`, forced **private**, and
  are **never** given a slug — so they can never appear in a catalog or at a
  public `/?play=…` URL.
* Live reads never see test rows, and test reads never see live rows. A test key
  calling `GET /v1/worlds/{id}` on a live world's id gets `404`, and vice versa.
* Usage counters are tracked per mode, so test traffic never moves your live
  numbers.

## Cost & quota

* **Test usage is never billed**, and that is permanent. Even after runtime
  billing is enabled, test-mode `session_seconds` are excluded.
* The generative endpoints above **consume no daily quota** in test mode (they
  short-circuit before the quota reserve), so they're effectively uncapped — go
  ahead and hammer them in CI.
* The one metered path is session-token **minting** (`POST /v1/sessions/token`):
  it still counts against a *separate* test-namespace `session` counter (same
  default caps), but is never billed.

## Idempotency across modes

`Idempotency-Key` is scoped per **(app, mode, key value)** — test and live keep
separate idempotency caches, so reusing the same key value across a test call and
a live call on the same app is safe (each replays only within its own mode).

## Before you ship

* Swap `sk_test_…` → `sk_live_…` (and `pk_test_…` → `pk_live_…`). Nothing else
  in your request code changes.
* `async: true` is ignored by test keys (they always return the sync mock, so
  there's no job to poll and no webhook fires). Test the async/webhook path with
  a live key.
* Re-run your happy path against live once to confirm real covers, real slugs,
  and real play URLs come back where the mocks had placeholders.

<Note>
  See [Concepts → Keys & modes](/concepts) for the key model, and
  [Pricing](/pricing) for how live runtime billing works.
</Note>
