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.