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:
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).
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.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 gets404, 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_secondsare 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-namespacesessioncounter (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_…(andpk_test_…→pk_live_…). Nothing else in your request code changes. async: trueis 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.
See Concepts → Keys & modes for the key model, and
Pricing for how live runtime billing works.

