Skip to main content
Anyone, including an AI coding agent, can get a test key without an account. A person signs in later, with GitHub, to keep the app and get more room in the sandbox.

1. Get a key in one call

First check whether you already have one: if FLOW_MESSAGING_KEY is set (in the environment or .env), use it and skip this step.
The answer (201, a SandboxKey, shortened here) is a new app of its own:
  • key and claim_token are shown once. Save key as FLOW_MESSAGING_KEY and claim_token as FLOW_CLAIM_TOKEN, in the environment or a git-ignored .env. The claim token (and claim_url, which holds it) lets a person claim the app, so treat it like the key.
  • Join from a phone: open a sender’s address.link (on Telegram, tap Start), or send it the join_code.
  • Don’t call it again when you already have a key. Calls are limited per client address and network; going over answers 429 rate_limited with retry_after.
init does all of this: with no key in the environment or .env, it gets one itself, writes FLOW_MESSAGING_KEY and FLOW_CLAIM_TOKEN to .env, and prints the sandbox link and join code, the allowance and the expiry. To use a key you already have, pass init --key fk_test_....

2. The sandbox allowance

Test keys send only on Flow’s shared sandbox senders, and only to people who joined your app there. What an app may send there for free:
  • Only messages your agent sends count. Inbound messages are free.
  • A contact counts once it joins your app, and keeps counting after it leaves.
  • iMessage is not part of either allowance: iMessage lines are arranged with the Flow team and used with a live key.
  • The WhatsApp sandbox is not open yet; when it opens, the allowance covers it.
  • When a key from POST /v1/sandbox/keys expires, its keys stop working and its contact is removed from the sandbox. A person can still sign in and claim the app.
  • Going live: signed in, you make fk_live_ keys in the dashboard (Keys, Live) and connect your own Telegram bot; iMessage lines are arranged with the Flow team. WhatsApp is not available yet. See Going live.
See what is left with GET /v1/app (shortened):
In TypeScript: (await flow.app.retrieve()).allowance.

3. Sign in to keep the app

A person signs in with GitHub through the device flow (OAuth 2.0 device authorization, RFC 8628). It claims the app: its data and keys are kept, the expiry is removed, and the app moves under the person’s signed-in allowance of 3 contacts with 100 messages each. That allowance is one per person, shared by every app they own or claim (allowance.scope is person), and a person may claim up to 10 apps. The new key replaces the sandbox key, which stops working as soon as the new key is handed out. A claim through claim_url in a browser hands out no key, and it revokes the sandbox key unless the person ticks “Keep my agent’s current key working” (so whoever sent a person someone else’s claim link is left with no working key on their allowance). Kept, the key works with its expiry removed (an expired one works again when the app is claimed within 30 days of its expiry); revoked, the person makes a new key on the dashboard’s Keys page.

With the CLI

It reads FLOW_CLAIM_TOKEN from .env, prints a link and a short code (for example WDJB-MJHT), opens the browser and waits. Once the person approves, it replaces FLOW_MESSAGING_KEY in .env with the new key and removes FLOW_CLAIM_TOKEN. --no-browser skips opening the browser. Coding agents that cannot wait on a command: run npx @flow-engineer/messaging login --no-wait. It prints the link and code and exits. Show them to the person, and once they say they approved, run npx @flow-engineer/messaging login again to collect the key.

With curl

  1. Start the sign-in with the claim token. client_name is shown to the person on the approval page.
    Without a claim token, send the app’s test key instead (-H "Authorization: Bearer $FLOW_MESSAGING_KEY").
  2. Show the person verification_uri and user_code: they open the page, sign in and type the code you show them (a link alone never approves; verification_uri_complete opens the same page). Never show device_code.
  3. Poll every interval seconds:
    • pending: wait interval seconds and poll again. 429 rate_limited means slow down: wait retry_after seconds.
    • approved: the answer holds key (shown once). Save it as FLOW_MESSAGING_KEY in place of the sandbox key, and drop FLOW_CLAIM_TOKEN.
    • denied: the person refused. Stop.
    • expired: the codes ran out (after 15 minutes) or were already used. Start again.

With TypeScript

signIn polls for you, honouring interval and retry_after. For your own loop, use flow.device.authorize({ claimToken, clientName }) and flow.device.poll(deviceCode).

In a browser

claim_url opens a page where the person signs in and claims the app without the CLI.

The dashboard

Signed-in people manage their apps and keys at api.flow.engineer/admin (sign in with GitHub), including creating and revoking test and live keys. Google sign-in is not available yet.

Errors

Switch on error.type and read error.channel_code; each error also carries a hint and a doc_url (permission, authentication, rate_limited).