Rules
- Nothing in test mode reaches a real contact unless that person joined the sandbox through your app. Build and test freely.
- The modes never mix. Every object has
livemode. A test key cannot see live data or use a live sender (403permission), and webhook endpoints receive only their own mode’s events. - A key is shown once, when it is issued, and stored only as a hash. Get a test key with no account in one call (
curl -X POST https://api.flow.engineer/v0/sandbox/keys, ornpx @flow-engineer/messaging init); signed in, you make live keys (fk_live_...) in the dashboard for your own Telegram bot or WhatsApp number (iMessage lines are arranged with the Flow team). Signed-in people create and revoke keys in the dashboard; otherwise ask the Flow team to revoke a key if it leaks. - Test mode has a sandbox allowance. Without an account: 1 contact, 50 messages in total, and the key expires after 7 days (
api_key.expires_at). Signed in with a Flow account: 3 contacts, 100 messages each, no expiry, one allowance per person shared by all their apps. It covers the Telegram sandbox (and WhatsApp when its sandbox opens), not iMessage, and counts only messages your agent sends. Your own account’s apps get the free trial instead, from your first sign-in at flow.engineer: up to 25 people per channel on Flow’s shared senders, the shared iMessage line included, with messages not capped (see Pricing); an app claimed from a sandbox key keeps the allowance.GET /v0/appreturns what is left (allowance). See Keys and sign-in. - Keep live keys on your server. Never ship a key in a web page or a mobile app.
Simulated contacts: test without a phone
A test key can also play the person’s side, so an automated test suite (or an agent checking its own work) runs end to end with no phone and no real channel. A simulated contact exists only inside Flow, on Telegram, WhatsApp, iMessage or SMS.- Each turn reaches your agent exactly like a real message:
message.receivedby webhook, on the stream and in the event log, in order, withsimulated: trueon the conversation. - Your agent replies with its normal sends. They pass the real send gate (the
channel’s kinds and fields, text limits, edit targets) and end
message.senton your app’s simulated sender (kind: simulated), which never touches a network. A kind the channel does not take answers422unsupported_content, as on a real sender. - A turn is a kind object in
content:basic(textand/ormediabyfile_id),voice,location,contact_card, orbutton_reply(a tap:message_idof your buttons, carousel or list message and the tappedbutton_id; Flow fills in the label). - Free: simulated conversations take no sandbox contact slot and use no
messages, since nothing leaves Flow. That holds on an app made without an account
too: its one contact stays free for your own phone, so test on the app you have
rather than making a second one. Simulated conversations, contacts and the events’
conversationcarrysimulated: true; real ones have nosimulatedfield. The key’s request rate limit still applies, and the gate skips the rules that protect a real number (pacing, new-contact budgets, abuse throttling), so tests can run in parallel. - SMS rules hold for the contact. Before a simulated SMS contact’s first turn,
a send to them is a start that needs
consentonPOST /v0/messages, as on a real number (403permission,consent_required). A turn with the text STOP opts the contact out (opted_out_viakeyword; sends answercontact_opted_out) and START lifts it; the transcript shows no automatic keyword reply. A simulated SMS sender keeps no quiet hours, so a test never depends on the time of day, and it sends media as MMS. - WhatsApp rules hold too. The contact’s turns go through the real WhatsApp
inbound path, so each turn opens the 24-hour window; outside it (also before the
contact’s first turn) only a
templatecan be sent (409outside_window). Buttons take up to 3 reply buttons (alistup to 10 rows); reactions andreply_towork; edits and unsends are refused (422unsupported_content). - iMessage group chats can be simulated.
POST /v0/test/conversationswith{"channel": "imessage", "group": {"name": "Weekend trip", "participants": [{"name": "Asha"}, {"name": "Ben"}]}}makes agroupconversation (2 to 32 participants, each a simulated contact). Each turn names its writer:from(a participant’scontact) is required on/inboundthere. The group rules hold: reactions, typing and read receipts are refused (422unsupported_content,group_unsupported). - HIPAA accounts simulate only SMS: a simulated Telegram, WhatsApp or iMessage
conversation, or a turn in one made before the flag was set, answers
403permission(hipaa_channel_not_covered). - Live keys get
403permission(test_mode_only).GET /v0/senderslists simulated senders only withkind=simulated.
flow.test.createConversation({ channel: "sms" }),
flow.test.inbound(convId, "hi") and flow.test.transcript(convId).
Going from test to live
Your code does not change. Connect your own Telegram bot or WhatsApp number (or have the Flow team connect an iMessage line), switchFLOW_MESSAGING_KEY to a live key, and register your webhook endpoint again with the live key. See Going live.