fn_live_… (spends your balance) and fn_test_… (never spends —
see Test mode). When you’re signed in here, your real keys are
inlined into every sample; otherwise export them first:
If you only have a live key: dashboard signup is the only self-serve way to get
the
fn_test_ twin today — there is no API to mint keys (GET /v1/keys lists,
nothing creates). Hand-provisioned accounts (design partners, evaluations) get keys
minted by an operator; if yours came without the test key, ask your operator contact
for it rather than developing against real money. See
Test mode → Getting a test key.Step 0 — prove your key works ($0)
GET /v1/me returns the calling key’s identity, caps, and balance without spending:
key.prefix is a 12-char fingerprint (the
mode prefix plus the first 4 chars of the tail — fn_live_8snw, not the bare
literal fn_live_), so branch on prefix.startsWith("fn_test_") or read mode
from GET /v1/keys; and the two cap fields are absent when no cap is set —
budget-guard code should treat undefined as “uncapped”, not as an error.
Step 1 — get a quote (instant, free)
POST /v1/sessions/estimate is free and always callable — no charge, no balance
required, works on test keys and zero-balance accounts. It returns what the parse
understood, which merchant domains would be dispatched (the list, not a count — you
see the aim before money moves), and the estimated cost range:
parsed.confidence is a string-wrapped float today — a
documented divergence; on events, confidence is
a JSON number 0–1, which is the canonical form.)
would_run: false means we would decline to spend on this query — stated up front,
before you commit a budget. This is the pre-spend gate to build on: if you create
the session anyway, you get a 201 and a run that ends abstained at $0 within
seconds, never a synchronous error. See
declined_to_spend.
Step 2 — stream a session (instant, $0, real event shapes)
Yourfn_test_ key streams a recorded real session through the same SSE path with
the same event shapes as production — so you can build your whole event loop before
spending a cent. Note the key and the Accept header:
search-intent (what the parse understood),
search-resolve (the merchant fan-out), orchestrate decisions (every dispatch and
decline, with the reason), search-source (attributed rows with per-source cost),
search-answer (the adjudicated result), and search-done (how the run stopped, with
any unchecked merchants stated). The vocabulary is documented in
The event stream.
Step 3 — the real run (tens of minutes; that’s normal)
Same call, live key. Expect the stream to go quiet for stretches — between the fan-out plan and the first harvested rows, the agent is driving real browsers, and the only traffic is orchestrator decision events and heartbeat pulses. Silence is not a hang; any event, whatever its phase, means the run is alive. Measured real runs: first rows in ~4–17 minutes, whole sessions 11–68 minutes end to end.max_spend_usd is required on every spending call: it’s this run’s reservation and
hard ceiling. The run can never charge past it, and it settles at measured actuals —
usually below the cap. The Idempotency-Key makes a network-timeout retry safe: a
replay returns the original response and never bills twice.
If you’d rather not hold a socket for the better part of an hour, drop the Accept
header — the call returns 201 with the session id immediately:
200 always means the
run reached a terminal, 202 + Retry-After means poll again):
timeout=240 and loop on the 202, not one timeout=600 call. Stock
Node fetch (undici) gives up on response headers after 300 seconds — a single
600-second block dies at exactly 5 minutes with an opaque TypeError: fetch failed
while the run continues server-side. ≤ 240 keeps every mainstream HTTP client
happy; the 202 + Retry-After loop is the designed pattern
(details).
All the ways to wait — stream, long-poll, webhooks — are compared in
Streaming, long-poll, or webhooks.
After the run: the receipt
Every charged dollar traces to a signed per-source receipt:Troubleshooting
- Long-poll dies at exactly 5 minutes in Node? That’s undici’s default 300 s
headers timeout, not the API. Use
timeout ≤ 240and loop on the202, or configure an undiciAgentwithheadersTimeout: 0. - Node behind a proxy: Node’s built-in
fetchignoresHTTPS_PROXYenv vars. In proxied environments (CI, some VPCs), setNODE_USE_ENV_PROXY=1or your API calls will fail with opaque 403s whilecurlworks. - Stream dropped mid-run? The run keeps going server-side. Reconnect to
GET …/streamwith aLast-Event-IDheader, or pageGET …/events?after=— same sequence numbers, nothing lost. - Got a
409on a follow-up turn? That’s not a failure — it’s a priced proposal waiting for your confirmation. See Budgets and priced proposals.