Skip to content

Ask the docs

Answers come only from these docs and are written by an AI model (Gemini), so check the linked pages. Your question is not kept. Do not include personal details or keys.

Test and live mode

Test & live mode · messaging API test mode · Telegram bot sandbox · API key test live

Keep sandbox data and real customer conversations separate.

Every API key belongs to one app and one mode, and the mode decides who your agent can reach.

curl https://api.flow.engineer/v0/app -H "Authorization: Bearer $FLOW_MESSAGING_KEY"
# "livemode": false -> a test key
Test modeLive mode
Keyfk_test_...fk_live_...
SendersThe shared sandbox sendersYour dedicated senders
Who can be messagedOnly people who sent your app’s join codeAnyone the channel’s rules allow
DataTest conversations, events, files and webhook endpointsLive ones
  • 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 (403 permission), 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, or npx @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/app returns 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.

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.

# A conversation with a new simulated contact (made-up address; one per test case)
curl -s https://api.flow.engineer/v0/test/conversations -H "Authorization: Bearer $FLOW_MESSAGING_KEY" \
-H "Content-Type: application/json" -d '{"channel": "sms"}'
# Write as the contact (media: upload with POST /v0/files, then pass file_id)
curl -s https://api.flow.engineer/v0/test/conversations/conv_.../inbound -H "Authorization: Bearer $FLOW_MESSAGING_KEY" \
-H "Content-Type: application/json" -d '{"content": {"kind": "basic", "text": "Do you deliver on Sundays?"}}'
# Both sides, oldest first: what was sent is exactly what the contact sees
curl -s https://api.flow.engineer/v0/test/conversations/conv_.../transcript -H "Authorization: Bearer $FLOW_MESSAGING_KEY"
  • Each turn reaches your agent exactly like a real message: message.received by webhook, on the stream and in the event log, in order, with simulated: true on 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.sent on your app’s simulated sender (kind: simulated), which never touches a network. A kind the channel does not take answers 422 unsupported_content, as on a real sender.
  • A turn is a kind object in content: basic (text and/or media by file_id), voice, location, contact_card, or button_reply (a tap: message_id of your buttons, carousel or list message and the tapped button_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’ conversation carry simulated: true; real ones have no simulated field. 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 consent on POST /v0/messages, as on a real number (403 permission, consent_required). A turn with the text STOP opts the contact out (opted_out_via keyword; sends answer contact_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 template can be sent (409 outside_window). Buttons take up to 3 reply buttons (a list up to 10 rows); reactions and reply_to work; edits and unsends are refused (422 unsupported_content).
  • iMessage group chats can be simulated. POST /v0/test/conversations with {"channel": "imessage", "group": {"name": "Weekend trip", "participants": [{"name": "Asha"}, {"name": "Ben"}]}} makes a group conversation (2 to 32 participants, each a simulated contact). Each turn names its writer: from (a participant’s contact) is required on /inbound there. The group rules hold: reactions, typing and read receipts are refused (422 unsupported_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 403 permission (hipaa_channel_not_covered).
  • Live keys get 403 permission (test_mode_only). GET /v0/senders lists simulated senders only with kind=simulated.

In TypeScript: flow.test.createConversation({ channel: "sms" }), flow.test.inbound(convId, "hi") and flow.test.transcript(convId).

Your code does not change. Connect your own Telegram bot or WhatsApp number (or have the Flow team connect an iMessage line), switch FLOW_MESSAGING_KEY to a live key, and register your webhook endpoint again with the live key. See Going live.