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

# The send gate: channel rules, new-contact limits and warm-up

> Every send passes one gate that applies each channel's rules: who may be messaged first on Telegram and iMessage, WhatsApp's 24-hour window and templates (once WhatsApp is available), new-contact budgets, warm-up, pacing and abuse protection.

Every send, whether over HTTP, the live stream or a webhook reply, passes one **send gate** that applies the channel's rules before anything leaves Flow; this page lists those rules.

```ts TypeScript theme={null}
import { OutsideWindowError, template } from "@flow-engineer/messaging";

// WhatsApp (not available yet; shown for when it is):
try {
  await conversation.send("Your table is ready.");
} catch (err) {
  if (err instanceof OutsideWindowError) {
    // More than 24 hours since they last wrote. Only a template may go.
    await conversation.send(template("tpl_01JB8ZC3K5M7P9R1T3V5X7Z9B1", "en", { body: ["Table 12"] }));
  } else throw err;
}
```

## Rule 1: is the conversation open?

| Channel | You may send free-form content when... | Otherwise |
| - | - | - |
| WhatsApp (not available yet) | the contact's last message is less than 24 hours old (the **window**) | only an approved `template`; anything else gets `409` [`outside_window`](/errors/outside_window) |
| iMessage | the contact has written to this line before | a new contact is refused with `403` [`permission`](/errors/permission): Flow's iMessage lines are reply-only today |
| Telegram | the contact has started your bot | Telegram refuses the message itself |
| Sandbox senders | the contact joined through your app with its join code | refused |

On WhatsApp, `conversation.window_open_until` (on the conversation and on every event) says when the window closes. Subscribe to `conversation.window_closing` to hear about it 1 hour before.

## Rule 2: starting a conversation spends budget

Writing to someone first (`POST /v1/messages`) spends the sender's **new-contact budget**:

* **Per sender**: a number of new contacts per day and per hour, in `sender.limits`.
* **Warm-up**: a new sender that writes first starts with a small daily budget that grows day by day while it is `warming_up`.
* **WhatsApp** (once available): also the number's messaging tier (`sender.limits.whatsapp_tier`).
* **Telegram**: a bot can write only to people who started it, so in practice its budget is not the limit.
* **iMessage**: Flow's lines are reply-only today, so writing first gets `403` [`permission`](/errors/permission).

When the budget is used up the send fails with `429` [`new_contact_limit`](/errors/new_contact_limit) and `retry_after` in seconds. A message to someone who already has an open conversation with that sender goes into it and spends nothing.

## Rule 3: abuse protection

The gate watches for signs that a sender is being used for unwanted messages:

* the same text going to many new contacts;
* a high share of new conversations that get no reply;
* people blocking the sender;
* a drop in WhatsApp's quality rating (once WhatsApp is available).

When one trips, the sender is **throttled**: until `throttled_until`, starting conversations fails with `429` [`sender_throttled`](/errors/sender_throttled), while replies into existing conversations still go. You receive `sender.status_changed` with a one-sentence `reason`, and another when the sender recovers by itself.

## Rule 4: pacing and order

* Each sender has a sending rate (pacing): only so many messages a second, from all your conversations together. A send over it is refused with `429` [`rate_limited`](/errors/rate_limited) and `retry_after`; wait that long and retry with the same idempotency key.
* At most one message per conversation is in flight at a time, and messages go out in the order you sent them.

## Write agents that respect the gate

* **Reply, don't broadcast.** Agents that answer people who wrote first never meet rules 2 and 3.
* **Check before you send.** `GET /v1/capabilities?conversation=conv_...` returns `window.open` and what each content type would do.
* **Retry only what clears by itself.** `new_contact_limit`, `sender_throttled` and `rate_limited` carry `retry_after`; `outside_window` will not clear until the person writes again, so wait for them to message the line (iMessage), or send a template instead (WhatsApp, once available).
* **Watch `sender.status_changed`.** It is the early warning before a channel acts against a number.

## Related

* [Senders](/concepts/senders): statuses and limits.
* [Going live](/guides/going-live): templates and dedicated numbers.
* [Rate limits](/concepts/rate-limits): request limits per API key, which are separate from the gate.


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