# Flow Messaging > Flow Messaging is a two-way messaging API for AI agents on Telegram and iMessage (WhatsApp coming). Flow hosts the senders (bots, lines, numbers), delivers every inbound message to your webhook or WebSocket as a signed, ordered event, and passes every send through one gate that enforces each channel's rules. Base URL: https://api.flow.engineer. Auth: `Authorization: Bearer $FLOW_MESSAGING_KEY` (`fk_test_...` for the sandbox, `fk_live_...` for your own senders). **No key? `curl -X POST https://api.flow.engineer/v1/sandbox/keys`**: no account, no API key sent. Save `key` as `FLOW_MESSAGING_KEY` and `claim_token` as `FLOW_CLAIM_TOKEN` (both shown once; e.g. in `.env`, gitignored), then show the person a sender's `address.link` and `join_code` from the answer. Check `FLOW_MESSAGING_KEY` first: never get a new key when you already have one. Key facts for coding agents: - Keys: `POST /v1/sandbox/keys` (no account) answers `201` with `key` (`fk_test_...`), `claim_token` (`fct_...`), `claim_url` (holds the token; treat it like one), `app` (with `sandbox_join_code`), `api_key.expires_at`, `allowance` and `senders` (the shared sandbox senders, each with `address.link` and `join_code`). Rate limited per client address (`429 rate_limited`, `retry_after`). The CLI does the same: `npx @flow-engineer/messaging init` (writes both to `.env`, prints the sandbox link and join code). Read the key from `FLOW_MESSAGING_KEY`; the webhook secret from `FLOW_MESSAGING_WEBHOOK_SECRET`. - Sandbox allowance: without an account, 1 contact and 50 messages in total on the Telegram sandbox (WhatsApp's when it opens), and the key expires after 7 days (then it stops working and the contact is removed; a person can still claim the app). Signed in: 3 contacts x 100 messages each, no expiry, one allowance per person shared by every app they own or claim (up to 10 claimed apps). iMessage is in neither. Only outbound messages count. `GET /v1/app` returns `allowance` (`tier`, `channels`, `contacts`, `messages_per_contact`, `messages` {`limit`, `used`, `remaining`}, `expires_at`, `upgrade`). - Sign in to keep the app (GitHub or Google): `npx @flow-engineer/messaging login` (uses `FLOW_CLAIM_TOKEN`, replaces `FLOW_MESSAGING_KEY` in `.env`; `--no-wait` for agents that cannot block, then run `login` again after approval). Or `POST /v1/device/authorizations {"claim_token":"fct_...","client_name":"Claude Code"}`, show the person `verification_uri` and `user_code` (never `device_code`): they open the page, sign in and type the code you show them (a link alone never approves), then poll `POST /v1/device/token {"device_code":"fdc_..."}` every `interval` seconds (`429` = slow down) until `approved` (new `key`: replace `FLOW_MESSAGING_KEY` with it, since the old sandbox key stops working once the new one is handed out; drop `FLOW_CLAIM_TOKEN`), `denied` or `expired` (start again). The app and its data are kept. A claim through `claim_url` in a browser gives no new key and revokes the sandbox key, unless the person ticks "Keep my agent's current key working" (then it works without expiry; an expired one works again if claimed within 30 days). After a claim that revoked it, ask the person for a key from the dashboard's Keys page, or sign in with the device flow above. - Allowance errors: `403 permission` with `channel_code` `sandbox_allowance_used`, `sandbox_contact_limit`, `sandbox_channel_not_included` or `sign_in_required`; an expired sandbox key is `401 authentication`, `sandbox_key_expired`. Fix: have a person sign in (an expired key still claims its app for 30 days), or get a new key. - Dashboard: https://api.flow.engineer/admin (sign in with GitHub or Google). Signed in, you can make live keys (`fk_live_...`) yourself (switch to Live, then Keys > Create live key: https://api.flow.engineer/admin/keys?mode=live; a sandbox app is claimed first) and go live on Telegram with your own bot; keep the live key in its own variable (for example `FLOW_MESSAGING_LIVE_KEY`) so it does not replace your test key. iMessage lines and WhatsApp numbers are arranged with the Flow team. - Join the Telegram sandbox: the shared bot is in the key's `senders` (or `GET /v1/senders?channel=telegram`); open its `address.link` (`https://t.me/?start=`) on a phone and tap Start to join your app (or send its `join_code`, e.g. `join wild-otter-04508705`). Then write to the bot: you get `message.received`. - Receive events on a webhook (public HTTPS only) or the WebSocket `GET wss://api.flow.engineer/v1/stream` (no public URL; use it for local development). The stream also takes `send` frames. - Reply into conversations (`conv_...`) with `POST /v1/conversations/{conversation_id}/messages`, in the webhook answer as `{"reply": }`, or with a stream `send` frame. - Verify `Flow-Signature` (`t=,v1=`, keyed with the `whsec_...` secret) before parsing a webhook body; dedupe on `event.id`; answer 2xx within 10 seconds. - Send `Idempotency-Key` on every POST; switch on `error.type`, never on the message text. Read `error.hint` first: it says what to change for this case; `error.doc_url` (`https://api.flow.engineer/docs/errors/`) is the type's page. - Every send ends `message.sent` or `message.failed`. Telegram never sends `message.delivered` or `message.read`. Text caps: 4096 characters on Telegram, 9999 on iMessage. - Go live on Telegram: with a live key, `POST /v1/senders {"channel":"telegram","telegram_bot_token":"..."}` connects your own bot (`200`, the sender `active`; connecting the same bot again updates that sender), and `DELETE /v1/senders/{sender_id}` disconnects it. Read the token from an env var; never paste it into a chat or commit it. - iMessage lines are connected by the Flow team and listed only with a live key. The person always writes first (replies only); get them to write with the line's opt-in link (`address.link` of the sender). - Build the app on the REST API (HTTP and the WebSocket stream) for everything it does at runtime: receive, reply, start conversations. `@flow-engineer/messaging`: its CLI (`npx ... init`, `login`) works; its TypeScript SDK is coming soon, so use HTTP until it is published. - MCP server (optional, for development): `https://api.flow.engineer/mcp`, with the same key as a Bearer token. MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the SDK once published) for everything it does at runtime. 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. For Claude Code the command is `claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY"` (Codex and Cursor: section 12 of https://api.flow.engineer/docs/quickstart.md). If its tools are already available to you, use them for sandbox testing; after a test send, wait for `message.sent` or `message.failed`. ## Docs - [Agent quickstart](https://api.flow.engineer/docs/quickstart.md): everything needed to integrate, with curl, TypeScript and Python: getting a key in one call, the sandbox allowance, signing in to keep the app, the Telegram sandbox, the WebSocket stream, webhooks and signature checks, `message.received`, replies, your own Telegram bot, idempotency, errors, rate limits, channel notes (Telegram, iMessage), the optional MCP server. - [OpenAPI spec](https://api.flow.engineer/openapi.yaml): the full contract (OpenAPI 3.1, version 2026-11-01): every endpoint, schema, event type and error type. - [Error types](https://api.flow.engineer/docs/errors): one page per `error.type` (what it means, why it happens, how to fix it), the page each error's `doc_url` points at. ## Optional - [Get a key, allowance, sign in](https://api.flow.engineer/docs/quickstart.md#0-get-a-test-key-one-call-no-account): one call without an account; what it allows; the device flow. - [Telegram sandbox](https://api.flow.engineer/docs/quickstart.md#2-join-the-telegram-sandbox-test-key): join from your phone in a minute. - [Local development](https://api.flow.engineer/docs/quickstart.md#3-receive-events-websocket-local-dev-or-webhook): the WebSocket stream, or a tunnel for webhooks. - [Your own Telegram bot](https://api.flow.engineer/docs/quickstart.md#7-go-live-with-your-own-telegram-bot): connect a BotFather token with a live key. - [Channel notes](https://api.flow.engineer/docs/quickstart.md#11-channel-notes): Telegram and iMessage statuses, limits, buttons, reactions, edit and unsend. - [Errors and retries](https://api.flow.engineer/docs/quickstart.md#9-errors-switch-on-errortype): what each `error.type` means and whether to retry. ## Examples Plain HTTP, no SDK, TypeScript and Python, each tested end to end: - [Telegram echo (WebSocket stream, no public URL)](https://github.com/flow-engineer/sdk/tree/main/examples/telegram-echo) - [Telegram AI support agent (history per conversation, "Talk to a human" button, Idempotency-Key)](https://github.com/flow-engineer/sdk/tree/main/examples/telegram-ai-agent) - [Webhook receiver (Flow-Signature, dedupe on event.id, reply in the answer)](https://github.com/flow-engineer/sdk/tree/main/examples/webhook-receiver) - [Your own Telegram bot (POST /v1/senders with a live key)](https://github.com/flow-engineer/sdk/tree/main/examples/own-telegram-bot) With the TypeScript SDK (not published yet; until it is, use HTTP as above): - [Echo agent, 20 lines](https://github.com/flow-engineer/sdk/tree/main/examples/echo) - [Vercel AI SDK](https://github.com/flow-engineer/sdk/tree/main/examples/vercel-ai-sdk) - [OpenAI Agents SDK](https://github.com/flow-engineer/sdk/tree/main/examples/openai-agents) - [Claude Agent SDK](https://github.com/flow-engineer/sdk/tree/main/examples/claude-agent-sdk) - [Mastra](https://github.com/flow-engineer/sdk/tree/main/examples/mastra) - [LangChain.js](https://github.com/flow-engineer/sdk/tree/main/examples/langchain) - [Eve](https://github.com/flow-engineer/sdk/tree/main/examples/eve)