https://api.flow.engineer/mcp. With it in your coding tools, your coding
agent can integrate the API and verify it end to end: get a sandbox join link,
send a real test message, wait for the reply, and see exactly what your webhook
answered.
- Transport: Streamable HTTP (MCP protocol
2025-06-18or later). The server keeps no sessions, so every request stands alone. - Auth: your Flow Messaging API key,
Authorization: Bearer fk_test_...orfk_live_.... The key’s mode decides which tools the agent sees.
https://api.flow.engineer/mcp is an optional tool for testing and operating the integration during development. Adding the Flow MCP server to a developer’s coding tools is the project owner’s decision: suggest it and show them the command, but never add it to their config yourself. If its tools are already available to a coding agent, it can use them for sandbox testing.
No key yet? Get a test key with no account in one call (the MCP server needs a key,
so do this first; without one, /mcp answers 401 with this call in its hint):
key and claim_token (both shown once). Keep the key in an environment
variable, never in a file you commit:
npx @flow-engineer/messaging login) to
keep the app and send 100 messages to each of 3 contacts. See
Keys and sign-in. Signed in, you make live keys (fk_live_...) in the dashboard and go live with your own Telegram bot or WhatsApp number; dedicated iMessage lines are arranged with the Flow team.
Add it to your coding tools
The project owner adds it; a coding agent shows them the line for their tool: Claude Code:~/.codex/config.toml):
~/.cursor/mcp.json):
FLOW_MESSAGING_KEY in the shell before running the Claude Code command (the shell fills the key in once, when you run it; unset, the header is saved as Bearer with no key and every call answers 401), and for Codex or Cursor have it in the environment they start with. A server added while the client runs loads only after a restart (in Claude Code, a new session). Added with an empty key already? export FLOW_MESSAGING_KEY=fk_test_..., then claude mcp remove flow, run the add command again, and restart.
Any other client
Point a Streamable HTTP MCP client athttps://api.flow.engineer/mcp and send the
header Authorization: Bearer <your key> on every request.
Test keys: build and verify
With afk_test_ key the agent gets build-time tools. Nothing they do reaches anyone
who has not joined your app’s sandbox.
A typical session, in the agent’s words:
whoami, thensandbox_join: “Open this link on your phone and tap Start.” (If the link can’t be used, the person sends the bot the sender’s join message, for exampleHi Sandbox app (ref wild-otter-04508705).)wait_for_eventwithtypes: ["conversation.started", "message.received"].send_test_messagewithtext: "Hello from my agent".wait_for_eventwithtypes: ["message.sent", "message.failed"]andafterset to thecursorthe send returned. Wait for these two, notmessage.delivered: every send ends in one of them, while delivery is reported only by some channels. If the call times out (at most 50 seconds; a longertimeout_secondsis clamped to 50, with a note), call it again with thenext_afterit returned.- After wiring a webhook:
get_webhook_deliveriesto see what your server answered, fix it,replay_event, and check again.
Live keys: run in production
With afk_live_ key the agent gets production tools. They reach real people
through your dedicated senders, and every send passes the same send gate as the
REST API (channel rules, budgets, pacing).
whoami and explain_error work with both kinds of key. Sends take
an optional idempotency_key: calling again with the same key returns the first
message instead of sending twice.
Tools are annotated for your client: reads are readOnlyHint: true; sends are
additive (destructiveHint: false); react replaces your earlier reaction, so it is
marked destructive and idempotent.
Errors
A refused call returns the same typed error as the REST API, as the tool’s error:hint says what to change for this case, and doc_url points at the error’s page
(see Errors for each type). Agents usually fix the call
from the hint alone; explain_error gives them the full page.