Skip to main content
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: 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 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.
See Concepts → Keys & modes for the key model, and Pricing for how live runtime billing works.