CORS: open, and deliberate about resume
Every API response carries permissive CORS headers:Last-Event-ID is pre-allowed for
stream resume, and the response headers your retry logic needs (Retry-After,
fn-request-id) are exposed. This posture is a contract, not an accident — a change
would be a dated-version change announced in the changelog.
Open CORS is not an invitation to put an API key in a page. It exists so
scoped, short-lived credentials (below) can stream directly.
Why EventSource can’t connect
The native browser SSE client cannot set request headers, and every credential on
this API is Authorization: Bearer — there is no query-param or cookie auth
(?api_key=… returns 401 invalid_key). So EventSource, with its free
auto-reconnect and automatic Last-Event-ID bookkeeping, cannot reach the stream at
all.
Stream with fetch + ReadableStream instead: split on blank lines, accumulate
id: / data: fields, track the last id: yourself, and reconnect with a
Last-Event-ID header. Pair it with the
rebuild-then-attach reload recipe
and a hard page reload mid-run survives cleanly — the sequence numbers are shared
between the stream and GET …/events, and this combination is verified working
browser-direct.
Never ship a live key to a page
Anfn_live_ key in browser code (bundle, localStorage, a prop) is fully
spend-capable: runs:write starts real runs against your balance, and open CORS
means any origin that obtains the key can use it from anywhere. Treat fn_live_
(and fn_test_) exactly like a Stripe secret key: server-side only, always.
The recommended browser path: a scoped, short-lived token
The pattern to copy is the one the dashboard’s own playground uses — a playground token: a scoped bearer (fnpg_…) that can create at most one
session at or under a stated max_spend_usd, then read/stream/stop only that
session, and expires in 10 minutes (expiry is the revocation). Losing one to the
browser risks a single bounded run, not the account.
Today that token is minted only by the platform dashboard for its playground —
there is no public API endpoint to mint one yet (a stated gap; an ephemeral-token
endpoint is the planned fix). Until it ships, the browser architecture is:
- Your backend holds the key. The page never sees
fn_live_…. - Create server-side, stream through your own route. The backend POSTs
/v1/sessions, hands the page the session id, and proxiesGET …/streamthrough a route handler that injects theAuthorizationheader — ~20 lines in any framework; forward the client’sLast-Event-IDheader through for resume. - Reads can be proxied cheaply and freely — read GETs are unthrottled, so a
thin pass-through for
/eventsand/resultcosts you nothing.
Test keys in the browser
Anfn_test_ key never spends, which makes it tempting for demos — but it still
authenticates your whole account (reads, webhook management). Keep it server-side
too; use the proxy pattern for demos, with the test key behind it.