> ## 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.

# Senders: the shared sandbox and dedicated numbers, bots and lines

> A sender is what your AI agent talks from: a Telegram bot or an iMessage line (WhatsApp numbers are coming). Use Flow's shared Telegram sandbox bot while building and a dedicated sender of your own to go live.

A **sender** is what your agent talks from: a Telegram bot or an iMessage line (and a WhatsApp number once WhatsApp is available). Flow hosts every sender, so you never manage channel credentials.

| Channel | Shared sandbox (test key) | Dedicated (live key) |
| - | - | - |
| Telegram | Flow's sandbox bot | Your own bot: connect its token with `POST /v1/senders` |
| iMessage | None | A line the Flow team connects to your app; replies only |
| WhatsApp | Not available yet | Not available yet (waiting on Meta's approval) |

```bash theme={null}
curl https://api.flow.engineer/v1/senders \
  -H "Authorization: Bearer $FLOW_MESSAGING_KEY"
```

```json theme={null}
{
  "data": [
    {
      "id": "snd_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
      "channel": "telegram",
      "kind": "shared",
      "livemode": false,
      "status": "active",
      "address": { "username": "flow_sandbox_bot", "link": "https://t.me/flow_sandbox_bot?start=wild-otter-04508705" },
      "join_code": "join wild-otter-04508705",
      "limits": { "new_contacts_per_day": 50, "new_contacts_per_hour": 10 },
      "created_at": "2026-11-01T09:00:00Z"
    }
  ],
  "has_more": false
}
```

`address.link` opens a chat with the sender: `https://t.me/...` for a Telegram bot (and `https://wa.me/...` for a WhatsApp number, once available). On the Telegram sandbox bot the link carries your join code (`https://t.me/<bot>?start=<code>`), so opening it and tapping **Start** joins your app. For an iMessage line it is the line's opt-in link, which opens Messages with the line and a prefilled text the person sends to start the conversation; it is set only when the line has one configured.

## Two kinds of sender

| | Shared (sandbox) | Dedicated |
| - | - | - |
| Who uses it | Many apps, in test mode | Your app only, in live mode |
| Key | `fk_test_...` | `fk_live_...` |
| Who it can talk to | Only people who sent your app's join code | Anyone the channel's rules allow |
| Name people see | Flow's sandbox name | Your brand |
| How you get it | Already there | Telegram: `POST /v1/senders` with your bot's token. iMessage: ask the Flow team. See [Going live](/guides/going-live) |

## The sandbox and join codes

A shared sender serves many apps at once. A person joins **your** app by sending its join code, such as `join wild-otter-04508705`, to the sandbox sender. On Telegram, opening the sender's `address.link` and tapping **Start** does the same; typing `join <code>` is the fallback when the link can't be used. From then on their messages reach your app, and your app can message them.

* A join code is two words and eight digits. Your app's code is `sandbox_join_code` in `GET /v1/app` (the bare code, for example `wild-otter-04508705`); each shared sender shows what a person sends as `join_code` (`join wild-otter-04508705`).
* A join starts a conversation and sends you `conversation.started` with `via: "sandbox_join"`.
* Your test key cannot message anyone who has not joined. That is what keeps the sandbox safe for everyone.

## Status and limits

Each sender has its own status and budget for starting conversations:

| `status` | Meaning |
| - | - |
| `pending` | Requested; Flow is provisioning it (for senders Flow provisions, such as WhatsApp numbers once available). |
| `active` | Sending normally. |
| `warming_up` | Active, with a new-contact budget that grows day by day. |
| `throttled` | Slowed after an abuse signal; recovers by itself at `throttled_until`. Replies still go. |
| `flagged` | Flagged by the channel or by Flow; starting conversations is paused. A Telegram bot whose token Telegram rejected is `flagged` and holds its queued messages until you send the new token (`POST /v1/senders`); held messages fail with `outside_window` (`channel_code` `queued_too_long`) after 72 hours. |
| `banned` | It cannot send or receive: the channel banned it, you disconnected it (`DELETE /v1/senders/{sender_id}`), or its bot or line was connected to another app. |

`limits.new_contacts_per_day` and `limits.new_contacts_per_hour` are the sender's current budget for starting conversations; WhatsApp senders (once available) also show `quality_rating` and `limits.whatsapp_tier`. Every change arrives as a `sender.status_changed` event. The rules behind them are on [The send gate](/concepts/send-gate).

## Related

* [Conversations and channels](/concepts/conversations)
* [Test and live mode](/concepts/test-and-live-mode)
* [Going live](/guides/going-live)
* API: [List senders](/api-reference/senders/list-senders), [Request a dedicated sender](/api-reference/senders/request-a-dedicated-sender)


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