> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flow.engineer/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Flow Messaging is one HTTP API (https://api.flow.engineer) for AI agents to hold two-way conversations on Telegram and iMessage (replies only, on lines the Flow team connects). WhatsApp is coming and not available yet.
> Build the app on the REST API. MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the TypeScript SDK, which wraps it) for everything it does at runtime. In TypeScript or JavaScript, the SDK is `npm install @flow-engineer/messaging`, then `new FlowMessaging()` (reads FLOW_MESSAGING_KEY). Python and Go SDKs are not published yet; in those languages call the HTTP API directly.
> No key yet? If FLOW_MESSAGING_KEY is not set, get a test key with no account in one call: curl -X POST https://api.flow.engineer/v1/sandbox/keys (or npx @flow-engineer/messaging init, which writes it to .env). Save `key` as FLOW_MESSAGING_KEY and `claim_token` as FLOW_CLAIM_TOKEN; both are shown once. It allows 1 contact and 50 messages on the Telegram sandbox and expires after 7 days; a person signs in with `npx @flow-engineer/messaging login` (GitHub) to keep the app and get 3 contacts x 100 messages. Signed in, live keys (fk_live_...) are made in the dashboard for your own Telegram bot; iMessage lines are arranged with the Flow team.
> The hosted MCP server (https://api.flow.engineer/mcp) is optional, for testing 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. The command for Claude Code: claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY". If its tools are already available to you, use them for sandbox testing.
> Authenticate with `Authorization: Bearer fk_test_...` (sandbox) or `fk_live_...` (real contacts). Never put a live key in client-side code.
> Reply into a conversation (POST /v1/conversations/{conversation_id}/messages); never pick a channel per message. Switch on `error.type`, not the message text.
> Full documentation for agents: https://docs.flow.engineer/llms-full.txt. OpenAPI spec: https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml.

# For AI coding agents: integrate Flow Messaging with Claude Code, Codex or Cursor

> Instructions for coding agents (Claude Code, Codex, Cursor, ChatGPT) adding Telegram or iMessage to an AI agent with Flow Messaging: install, environment variables, the MCP server, the skill, and the rules to follow.

This page is written for AI coding agents: follow it step by step to give the user's AI agent Telegram or iMessage with Flow Messaging.

What exists today: Telegram is live (the shared sandbox bot with a test key, the user's own bot with a live key). iMessage is live for replies only, on lines the Flow team connects to the user's app; it is not in the sandbox. WhatsApp is not available yet (it waits on Meta's approval), and there is no SMS. If the user asks for WhatsApp, say so and build on Telegram: the code does not change per channel.

<Prompt description="Paste this into Claude Code, Codex or Cursor to have it do the integration." actions={["copy", "cursor"]}>
  Add two-way Telegram messaging (and iMessage replies, if the Flow team has connected a line) to my AI agent using Flow Messaging. Follow [https://docs.flow.engineer/coding-agents.md](https://docs.flow.engineer/coding-agents.md) exactly: use FLOW\_MESSAGING\_KEY if it is set, otherwise get a test key yourself with `npx @flow-engineer/messaging init` (or `curl -X POST https://api.flow.engineer/v1/sandbox/keys`), use the TypeScript SDK `@flow-engineer/messaging` (or the HTTP API at [https://api.flow.engineer](https://api.flow.engineer) in other languages), receive `message.received` events, reply with my agent's answer through `event.conversation.reply(...)`, and verify it end to end in the sandbox before you finish. Build on the REST API; the Flow MCP server is an optional development tool that I add myself if I want it.
</Prompt>

## 1. Get a key and set up

First check for a key: if `FLOW_MESSAGING_KEY` is set (in the environment or `.env`), use it. Otherwise get a test key yourself, with no account, in one call. Do not invent a key, and do not get a second one when you already have one.

```bash theme={null}
npx @flow-engineer/messaging init
```

With no key in the environment or `.env`, `init` gets one (`POST /v1/sandbox/keys`) without asking, then:

* writes `FLOW_MESSAGING_KEY` and `FLOW_CLAIM_TOKEN` to `.env` and adds `.env` to `.gitignore`;
* prints the sandbox link and join code, the allowance and the expiry;
* with the user's approval (they are the project owner, so leave the answer to them and never pass `--yes` yourself), installs the Claude Code skill in `.claude/skills/flow-messaging/`, adds a Flow Messaging section to `AGENTS.md` (read by Codex and other agents), and registers the MCP server `flow` for Claude Code (`.mcp.json`) and Codex (`codex mcp add`).

Flags: `--yes` (no questions; the owner's choice, not an agent's), `--key fk_test_...` (use a key you already have), `--no-agent-files`, `--no-mcp`, `--no-codex`, `--dir <path>`.

Without the CLI, the same key by HTTP:

```bash theme={null}
curl -X POST https://api.flow.engineer/v1/sandbox/keys
```

Save `key` as `FLOW_MESSAGING_KEY` and `claim_token` as `FLOW_CLAIM_TOKEN` in `.env` (which must be in `.gitignore`); both are shown once. The answer also holds the app's `sandbox_join_code` and the sandbox `senders` with their links; later, `GET /v1/app` and `GET /v1/senders` give them again.

This key has a sandbox allowance: 1 contact and 50 messages in total on the Telegram sandbox, and it expires after 7 days. Tell the user, and offer to sign them in to keep the app (3 contacts x 100 messages, no expiry): run `npx @flow-engineer/messaging login --no-wait`, give them the link and code it prints, and once they say they approved, run `npx @flow-engineer/messaging login` to collect the new key. Full details, the curl version and the errors: [Keys and sign-in](/get-a-key).

Then add the SDK to a TypeScript or JavaScript project:

```bash theme={null}
npm install @flow-engineer/messaging
```

The Python and Go SDKs are not published yet: in those languages call the HTTP API directly, as each page's Python and Go examples show.

## 2. Environment variables

| Variable | Required | What it is |
| - | - | - |
| `FLOW_MESSAGING_KEY` | yes | The API key: `fk_test_...` while building, `fk_live_...` in production. Server side only. |
| `FLOW_CLAIM_TOKEN` | until signed in | The `claim_token` (`fct_...`) from `POST /v1/sandbox/keys`. `npx @flow-engineer/messaging login` uses it to claim the app, then removes it. |
| `FLOW_MESSAGING_WEBHOOK_SECRET` | with webhooks | The endpoint's signing secret, `whsec_...`, returned once by `POST /v1/webhook_endpoints` (and by `POST /v1/webhook_endpoints/{id}/rotate_secret` when you rotate it). |
| `FLOW_MESSAGING_BASE_URL` | no | Defaults to `https://api.flow.engineer`. |

Never hard-code keys, never commit `.env`, and never put a key in browser or mobile code.

## 3. The MCP server (optional, for development)

**Build on the REST API; the MCP server is for development.** MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the TypeScript SDK, which wraps it) for everything it does at runtime. Never make the app call the MCP server.

The hosted MCP server at `https://api.flow.engineer/mcp` (Streamable HTTP, the same API key as a Bearer token) is an optional tool for testing and operating the integration while you build: with a test key it shows the sandbox join link, sends test messages, waits for events, reads webhook deliveries and replays events; with a live key it reads and answers conversations. 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 you, use them for sandbox testing.

To suggest it, show the project owner the line for their tool. The server is named `flow` and reads the key from `FLOW_MESSAGING_KEY` in their environment:

Claude Code:

```bash theme={null}
claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY"
```

Codex (`~/.codex/config.toml`):

```toml theme={null}
[mcp_servers.flow]
url = "https://api.flow.engineer/mcp"
bearer_token_env_var = "FLOW_MESSAGING_KEY"
```

Cursor (`~/.cursor/mcp.json`):

```json theme={null}
{"mcpServers": {"flow": {"url": "https://api.flow.engineer/mcp", "headers": {"Authorization": "Bearer ${env:FLOW_MESSAGING_KEY}"}}}}
```

Test keys get `sandbox_join`, `send_test_message`, `wait_for_event`, `list_events`, `get_webhook_deliveries`, `replay_event`; live keys get `send_message`, `reply`, `react`, `typing`, `list_conversations`, `get_conversation_messages`; both get `whoami`, `capabilities`, `explain_error`. After a test send, wait for `message.sent` or `message.failed`, never `message.delivered` (Telegram never sends it).

`npx @flow-engineer/messaging mcp` is a stdio bridge to `https://api.flow.engineer/mcp` that reads `FLOW_MESSAGING_KEY` from the environment or `.env`, for clients that run MCP servers as commands. Full tool list: [MCP server](/mcp).

## 4. The skill

* **Claude Code:** `init` installs the skill in `.claude/skills/flow-messaging/`. It tells Claude how to integrate, which errors to handle and how to test.
* **Any agent:** a skill file is published at [https://docs.flow.engineer/skill.md](https://docs.flow.engineer/skill.md). Install it with `npx skills add https://docs.flow.engineer`.

## 5. Integrate: what to write

1. **Receive** `message.received` events. In a web app, add a webhook route with `flow.webhooks.handler({ onEvent })` (or `constructEvent` on the raw body). In a worker or script, use `for await (const event of flow.events.stream({ types: ["message.received"] }))`.
2. **Read** what the person said with `contentText(event.data.message.content)`; it covers text, captions, voice note transcripts and button taps.
3. **Answer** with `event.conversation.reply(x)`, where `x` is a string or the user's LLM stream (OpenAI, Anthropic, Vercel AI SDK, OpenAI Agents SDK, Claude Agent SDK, LangChain, Mastra). Pass `{ idempotencyKey: event.id }`.
4. **Register** the webhook: `POST /v1/webhook_endpoints` with the public URL and `["message.received"]`; save the returned secret as `FLOW_MESSAGING_WEBHOOK_SECRET`. Locally, with no public URL, read events from the live stream instead: `GET /v1/stream` (WebSocket, resumable with `after`; `flow.events.stream(...)` in TypeScript). See [Local development](/guides/local-development).
5. **Handle errors** by `error.type` (TypeScript: `err instanceof OutsideWindowError` etc.). Each error carries a `hint` and a `doc_url` such as [https://api.flow.engineer/docs/errors/outside\_window](https://api.flow.engineer/docs/errors/outside_window).

```ts TypeScript (minimal complete integration) theme={null}
import { FlowMessaging, contentText } from "@flow-engineer/messaging";
import { runAgent } from "./agent"; // the user's existing agent: returns a string or an LLM stream

const flow = new FlowMessaging();

export const POST = flow.webhooks.handler({
  onEvent: async (event) => {
    if (event.type !== "message.received") return;
    const input = contentText(event.data.message.content);
    await event.conversation.reply(await runAgent(input, { conversationId: event.conversation.id }), {
      idempotencyKey: event.id,
    });
  },
});
```

## 6. Verify end to end

Do not finish until this works:

1. Ask the user to join the sandbox from their phone, if they have not: give them the sandbox sender's link from `sandbox_join` (or `GET /v1/senders`). On Telegram, opening the link and tapping **Start** joins; otherwise they send `join <code>`, for example `join wild-otter-04508705`.
2. Start the app (reading `GET /v1/stream` when developing locally without a public URL).
3. If the Flow MCP tools are available to you, call `send_test_message` or ask the user to send a message, then `wait_for_event` for `message.received`. Without them, ask the user to send a message and read it with `GET /v1/events?type=message.received`. After a send, wait for `message.sent` or `message.failed` (not `message.delivered`, which not every channel reports). Each `wait_for_event` call waits up to 50 seconds (a longer `timeout_seconds` is clamped to 50, with a note, not refused); if it times out, call it again with `after` set to the `next_after` it returned.
4. If a delivery failed, read it with `get_webhook_deliveries` (MCP), fix the handler, and `replay_event`; without the MCP tools, fix the handler and have the user write again.

## Rules

* **Build on the REST API (or the TypeScript SDK);** the MCP server is never a runtime dependency.
* **Use the SDK in TypeScript/JavaScript;** do not hand-roll HTTP calls there.
* **Reply into the conversation** (`event.conversation`, or `POST /v1/conversations/{conversation_id}/messages`). Never choose a channel per message and never assume a contact has a phone number.
* **Verify webhook signatures** on the raw body before trusting it; deduplicate on the event `id`.
* **Answer webhooks within 10 seconds.** Run slow agents after the response (`after()`, `waitUntil()`, a queue) or use the event stream.
* **Do not send unsupported content blindly.** Set `fallback: "auto"` or check `GET /v1/capabilities`. `reply()` already sets it.
* **Never message first on iMessage:** the person writes first; on `outside_window`, wait for them to write, do not retry the same content. (WhatsApp, once available: send an approved `template` instead.)
* **Test keys only while building.** Do not switch to `fk_live_` unless the user asks.
* **Do not work around the sandbox allowance.** On `403 permission` with `channel_code` `sandbox_allowance_used` or `sandbox_contact_limit`, or `401 authentication` with `sandbox_key_expired`, do not retry and do not get more keys: ask the user to sign in (`npx @flow-engineer/messaging login --no-wait`, then `login` once they approve).

## Docs for agents

| URL | What |
| - | - |
| [https://docs.flow.engineer/llms.txt](https://docs.flow.engineer/llms.txt) | Index of every page with a one-line description |
| [https://docs.flow.engineer/llms-full.txt](https://docs.flow.engineer/llms-full.txt) | The whole documentation in one file |
| Any page URL + `.md` | That page as Markdown (for example [https://docs.flow.engineer/quickstart.md](https://docs.flow.engineer/quickstart.md)) |
| [https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml](https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml) | The OpenAPI 3.1 spec |
| [https://github.com/flow-engineer/sdk](https://github.com/flow-engineer/sdk) | SDK source and examples |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.