Skip to main content
Test mode is a thin branch at the paid boundary, not a sandbox environment — there is no second engine and there are no simulated merchants. That’s a feature: what you integrate against is real.

Getting a test key

  • Dashboard signup mints the pair. Self-serve signup at platform.fiveninelabs.com creates your account with a “Default live” and a “Default test” key side by side — accounts created this way always have the fn_test_ twin.
  • Hand-provisioned accounts (design partners, evaluations) get keys minted per key by an operator. If your account came with only a live key, ask your operator contact for the test twin — don’t develop error handling against real money.
Stated gap: there is no API surface to mint a key — GET /v1/keys lists, PATCH/DELETE rename and revoke, but nothing creates. An account holding only a live key cannot self-serve its test twin today; closing this is an open item on our side.
On an fn_test_ key:
  • Free paths run for real. Parse, estimate, narrow, ask, session reads — the same code paths as production, actually executed (discover joins this list when it ships).
  • Paid paths serve recorded real sessions. A session or extraction create streams a replay of a real production run appropriate to your request shape — through the same SSE path, with the same event shapes, at $0 (replays are free by construction). Internal evidence URLs and operator metadata are scrubbed; the event spine is otherwise the real thing.
  • Webhooks deliver for real. Signed, retried, and logged like production — so the whole integration loop, including your webhook handler, wires up and runs in CI without spending a cent.
fn_test_ and fn_live_ share the account, scopes, caps, and webhook endpoints. Behavior differs only at the paid boundary — which is the point.

What test keys never do

Spend money, dispatch a pass, touch a live merchant, or mint ledger entries beyond $0 replay reads.

The magic inputs — rehearse every ending

Error handling should be rehearsed, not discovered in production. On an fn_test_ key, a query (sessions) or guidance (extractions) containing one of these sentinels reliably produces that outcome — same status codes, same envelopes, same event shapes as production: Sentinels are stable API surface (new ones may appear — the additive rule). They are inert on fn_live_ keys — matched only under a test key, so a production query containing the literal string is just a query.

Examples

Rehearse the out-of-credit path:
Rehearse the proposal flow end to end (create, then the first follow-up turn returns the 409; confirm it like a real one):
Rehearse an honest abstain on the stream:

A CI recipe

A complete integration test costs $0 and touches no live merchant:
  1. GET /v1/me on the test key — auth and scopes.
  2. POST /v1/sessions/estimate — the free quote path, run for real.
  3. POST /v1/sessions with an ordinary query — stream a replayed real session; assert your event loop handles the full spine through session-turn.
  4. One create per sentinel — assert your handling of the 402, the proposal confirm, the abstain, the decline (a $0 abstained run — not an HTTP error), the spend-budget stop, and the error terminal.
  5. Register a webhook endpoint and drive one replay run to its terminal — assert your signature verification and dedupe against the real delivery. (POST /v1/webhooks/{id}/test will make this a one-call step once it ships — it is planned, not yet available.)
If all five pass, the first thing your live key meets in production will be a code path you’ve already exercised.