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

# Conversations and contacts

> Reply to a conversation without selecting a channel for each message.

A **conversation** is one sender talking with one contact; your agent always replies into a conversation, so the channel is already decided.

```ts TypeScript theme={null}
// From an event: the conversation is attached and ready to use.
await event.conversation.reply("Thanks! Your order is on its way.");

// From an ID you stored earlier:
await flow.conversation("conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1").send("Your order has shipped.");
```

```bash curl theme={null}
curl https://api.flow.engineer/v0/conversations/conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1/basic \
  -H "Authorization: Bearer $FLOW_MESSAGING_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your order has shipped."}'
```

## The objects

| Object | ID | What it is |
| - | - | - |
| Sender | `snd_` | What your agent talks from: a Telegram bot, an iMessage line or a WhatsApp number. See [Senders](https://docs.flow.engineer/unified-messaging-api/concepts/senders.md). |
| Contact | `ct_` | A person on **one** channel: a phone number, a WhatsApp username, a Telegram user or an iMessage handle. |
| Conversation | `conv_` | One sender with one contact (`type` `direct`), or with an iMessage group chat (`type` `group`). Holds the window state and whether the contact opted in. |
| Message | `msg_` | One message in or out, of one [kind](https://docs.flow.engineer/unified-messaging-api/concepts/content-types.md) (`content.kind`), with a status. |
| Event | `evt_` | One entry in your app's ordered log. See [Events and webhooks](https://docs.flow.engineer/unified-messaging-api/concepts/events-and-webhooks.md). |

IDs are a prefix plus a ULID, so they sort by creation time.

## One channel per conversation

* **There is no cross-channel identity.** The same person on Telegram and on WhatsApp is two contacts and two conversations. Flow never guesses that they are the same person, and never moves a conversation to another channel.
* **Never assume a phone number.** A Telegram contact has a user ID, a WhatsApp contact may have only a username, and an iMessage handle may be an email address. Read `contact.address` for what the channel gives.
* **Reply where they wrote.** Each event carries `conversation.channel`, so one agent can serve every channel with the same code.

## What a conversation tells you

```json theme={null}
{
  "id": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
  "type": "direct",
  "channel": "telegram",
  "sender": "snd_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
  "contact": "ct_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
  "livemode": true,
  "last_inbound_at": "2026-11-03T10:15:00Z",
  "opted_in": true,
  "created_at": "2026-11-01T08:00:00Z"
}
```

* `window_open_until` (WhatsApp only; absent on Telegram and iMessage): until when free-form messages may be sent. After it, only a template. Every event repeats it in `conversation.window_open_until`.
* `type`: `direct` (one contact, in `contact`) or `group`. Read a missing `type` as `direct`.
* `group` (group conversations only): the group chat's `name`, when it has one, and its `participants`, each a contact. A group has no `contact`; each inbound message's `from` says who wrote. Group chats reach your app only from dedicated iMessage lines people add to them, and the API cannot create a group or change who is in it. See [iMessage](https://docs.flow.engineer/unified-messaging-api/guides/imessage-agent.md#7-group-chats).
* `opted_in`: the contact wrote first, joined the sandbox, or you recorded their consent. Always `true` on groups.
* `metadata`: up to 20 string pairs of your own (for example your user ID), kept with the conversation.

## Starting a conversation

Most conversations start when the person writes first. To write first, use `POST /v0/messages`: a `basic` or `template` message, with `kind` saying which, plus a sender and an address on that sender's channel. On Telegram that works only for people who already started your bot. iMessage lines reply only unless writing first is switched on for the line (`imessage.can_initiate`, available on request for dedicated lines). A start always makes a `direct` conversation.

```bash curl theme={null}
curl https://api.flow.engineer/v0/messages \
  -H "Authorization: Bearer $FLOW_MESSAGING_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender": "snd_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
    "to": { "telegram_user_id": "123456789" },
    "kind": "basic",
    "text": "Your order has shipped."
  }'
```

Any other `kind` answers `422 unsupported_content` with `param` `kind`: start with a basic message or a template, then send the other kinds into the conversation. `to` takes exactly one of `contact`, `phone`, `telegram_user_id` or `handle`. Starting a conversation spends the sender's new-contact budget, and each channel has its own rule for who may be messaged first. See [The send gate](https://docs.flow.engineer/unified-messaging-api/concepts/send-gate.md).

## Related

* [Message kinds](https://docs.flow.engineer/unified-messaging-api/concepts/content-types.md): what each channel takes, one endpoint per kind.
* [List conversations](https://docs.flow.engineer/api-reference/conversations/list-conversations.md) and [list a conversation's messages](https://docs.flow.engineer/api-reference/conversations/list-a-conversations-messages.md).


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