# Posting into a conversation

> Send a template or agent text, leave the agent a note, or instruct it to write the next message. The 24-hour window, openers, receipts.

`POST /v1/conversations/items` puts something into a person's thread from your
own system: a recruiter's note, a reminder sent as the agent, or an instruction
the agent turns into its next message. It also starts a thread with someone you
have never spoken to, by your number and theirs.

| `type` | What happens | Reaches the person | The agent reads it |
| --- | --- | --- | --- |
| `template` | An approved WhatsApp template is sent | Yes | As its own message |
| `agent_text` | Your text is sent as the agent's message | Yes | As its own message |
| `note` | Stored in the thread | Never | As background |
| `instruct` | The agent writes and sends its next message, acting on your text | Its message does | As an instruction |

## Who you post to

`to` is either a conversation or a pair of numbers:

```json
{"to": {"conversation_id": "5b0c…"}}
{"to": {"number": "+912248900123", "peer": "+919876543210"}}
```

`number` is one of your WhatsApp senders or phone numbers, in E.164. `peer` is
the person's number; a national-format one is read against `number`'s country.
The pair names the person's [contact](https://docs.talqing.com/observability/conversations.md)
`phone:<number>:<peer>`, the same one their phone calls, WhatsApp calls and
WhatsApp messages share — created if this is the first time.

The item goes into the thread you named, or else the person's open chat's
thread, or else their newest thread. With no thread at all, a new one is opened
with `status: "inactive"`: it stays out of the default inbox list until they
reply.

While the person has an open chat, anything but a note must go into that chat's
thread: their reply lands there, and an instruction elsewhere would have to end
the chat. Naming another conversation is a `409` with `code:
"chat_open_elsewhere"`, whose message names the open chat's conversation. Post
by `number` and `peer` and the open chat's thread is picked for you.

What each contact can receive:

| Contact | `template` | `agent_text` | `note` | `instruct` |
| --- | --- | --- | --- | --- |
| A phone contact whose number is a WhatsApp sender | Yes | Yes | Yes | Yes |
| A phone contact on a phone-only number | — | — | Yes | — |
| A Telegram chat | — | Yes | Yes | Yes |
| A WhatsApp user who hides their number | Yes | Yes | Yes | Yes |
| An [API chat](https://docs.talqing.com/channels/text-conversations.md) | — | Needs an open chat | Yes | Needs an open chat |
| A [media stream](https://docs.talqing.com/channels/media-streams.md) caller | — | — | Yes | — |

Anything else is a `422` with `code: "channel_unsupported"`.

## The request

```bash
curl -X POST https://api.talqing.com/v1/conversations/items \
  -H "Authorization: Bearer $TALQING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": {"number": "+912248900123", "peer": "+919876543210"},
    "type": "template",
    "template": {"id": "HX9f2c…", "variables": {"1": "Asha"}},
    "userdata": {"candidate_id": "c_123", "first_name": "Asha"},
    "author": {"id": "u_42", "name": "Priya (Recruiting)"},
    "idempotency_key": "followup-c_123-1"
  }'
```

| Field | Notes |
| --- | --- |
| `type` | `template`, `agent_text`, `note` or `instruct`. |
| `template` | `template` only: `id` is the Twilio Content SID (`HX…`) or the Gupshup template id, and `variables` fills every placeholder, button variables included — no more, no fewer. A template must be approved and sendable: [the same rules as a batch](https://docs.talqing.com/whatsapp/creating-a-batch.md). |
| `text` | The other three types: 1 to 4096 characters. |
| `on_window_closed` | `agent_text` and `instruct` on WhatsApp: `"fail"` (the default) or `{"opener": {"id", "variables"}}`. See below. |
| `userdata` | Not on a `note`. Merged into the person's userdata, and given to the agent that answers their reply. |
| `agent_id`, `agent_version`, `agent`, `agent_override`, `agent_team`, `vars`, `secrets` | Not on a `note`. The agent that answers this person, [as on an outbound call](https://docs.talqing.com/telephony/outbound-calls.md). Leave them out to continue whatever is running. See [Choosing the agent](#choosing-the-agent). |
| `author` | `{id, name}` of the person at your business who posted it. Shown in transcripts; never sent to the person, never shown to the agent. |
| `idempotency_key` | Up to 255 characters. Resending the same key returns the original item with `200` and `Idempotent-Replayed: true`; the same key with a different request is a `409`. |

The answer is `201` with the stored item, the same shape
`GET /v1/conversations/{conversation_id}/items` returns, with `post_type`
(`template`, `agent_text`, `note` or `instruction`), `author` and
`idempotency_key` set. **A valid request always stores its item**: a provider
that refuses the send answers `201` with `delivery_status: "failed"` and
`delivery_error`, not an error status.

## Notes and instructions

A note is background the agent reads from then on, in that thread and in any
later session that loads it — a phone call included:

```text
Note from the business (background for you; the person has not seen it):
She goes by Mira. Her interview is Tuesday at 3pm with Arjun.
```

An instruction runs a turn now. The agent reads your text as

```text
Instruction from the business: act on it in your next message to the person,
within your own instructions above. Never mention that you were instructed, or by whom.
```

and writes one message, in the conversation's language, through its own prompt,
tools and handoffs. It is delivered like any reply. On WhatsApp or Telegram with
no open chat, the instruction opens one as if the person had written: with the
agent an earlier post chose for their reply, else the agent on the number's
messages [trigger](https://docs.talqing.com/integrations/triggers.md) — refused with `422` `no_agent` when
there is neither. An API chat must be open (`409`
`chat_ended` otherwise).

Both reach the model as `<instructions>` below the agent's own prompt, so they do
not disturb the cached prompt. Neither carries a `channel`. Instructions are
turns: a message from the person during one replaces it (the instruction stays in
the history), and each answered instruction is billed as an
[answered message](https://docs.talqing.com/get-started/pricing-and-credits.md).

**An instruction is refused while the person is on a call with your agent**
(`409` `voice_session_live`). Notes are accepted, and reach the next session, not
the live call.

## Choosing the agent

**Send an agent to make it what answers this person; leave it out to continue
whatever is running.** A post takes the same agent fields as an
[outbound call](https://docs.talqing.com/telephony/outbound-calls.md), on WhatsApp and Telegram contacts:

| Open chat with them? | What happens |
| --- | --- |
| Yes, running the same agent | The post goes into it. |
| Yes, running something else | That chat ends (`close_reason: "replaced"`), then as below. |
| No | `instruct` opens a chat with your agent now. `template` and `agent_text` save it for their reply: **the agent answers when they write back**, and a later post with another agent replaces it. |

"The same agent" means the same `agent_id` (or inline definition), version,
override, `vars` and `secrets`. `userdata` is not part of it. A template does not
open a chat on its own, since most are never answered.

A chat started this way does not ask the [inbound webhook](https://docs.talqing.com/platform/inbound-webhook.md),
and keeps running if the number's messages trigger is pointed at another agent.
The number must have its messages trigger on, or the reply would never reach
Talqing (`422` `messages_off`).

## WhatsApp's 24-hour window

WhatsApp allows free-form messages only for 24 hours after the person last
messaged or called your number, answered or not. The conversation's
`ref.whatsapp_window_expires_at` says when it closes. Templates may go any time.

Outside the window, `agent_text` and `instruct` with `on_window_closed: "fail"`
are refused with `409` `whatsapp_window_closed`. With an opener:

```json
"on_window_closed": {"opener": {"id": "HX1b7…", "variables": {"1": "Asha"}}}
```

the opener template is sent at once and your message waits with
`delivery_status: "held"`. **Their next message releases it**, before the agent
answers them. A tap on the opener's own button releases it and starts no turn,
since the held message is the answer. One opener is in flight per person at a
time: a second held message waits behind the first's. A held message that is not
released within 7 days becomes `expired`; if the opener itself fails, the
messages held behind it fail too.

An instruction's reply follows the instruction's `on_window_closed`, whenever it
is written.

## Delivery

An outbound WhatsApp message moves through `delivery_status`:

| Status | Means |
| --- | --- |
| `pending`, `sending` | Stored, not sent yet / being sent |
| `held` | Waiting behind an opener for the person's reply |
| `skipped` | Never sent: the trigger's `reply_mode` is `none` |
| `queued` | Accepted by your BSP |
| `sent`, `delivered`, `read` | WhatsApp's receipts, in order. A late receipt never moves a message back |
| `undelivered`, `failed` | Not delivered; `delivery_error` has the reason and the provider's `code` |
| `expired` | Held for 7 days without a reply |

**A send the provider refuses is `failed` at once, a rate limit or a provider
outage included.** Nothing resends it: the provider may have delivered it before
failing, and a duplicate message is worse than a missing one. Post it again to
retry.

Telegram has no receipts: a message accepted by Telegram is `sent`. Every move of
an outbound WhatsApp or Telegram message — agent replies and batch templates
included — sends the [`conversation.item.status`](https://docs.talqing.com/api-reference/webhook-events.md)
webhook, carrying the `idempotency_key` you posted with.

## Errors

| Status | `code` | When |
| --- | --- | --- |
| `400` | — | Fields that do not fit the `type`, template variables missing, extra or invalid, a Twilio template WhatsApp has not approved, a number that is not yours |
| `404` | — | No such conversation, or no such approved template on the sender (Gupshup lists only approved ones) |
| `409` | `whatsapp_window_closed` | Free text outside the window with `on_window_closed: "fail"` |
| `409` | `voice_session_live` | An instruction while the person is on a call with your agent |
| `409` | `chat_ended` | `agent_text` or `instruct` on an API chat that has ended |
| `409` | `chat_open_elsewhere` | Anything but a note into a conversation other than the one holding their open chat |
| `409` | `idempotency_conflict` | The key was used for a different request |
| `422` | `channel_unsupported` | The contact cannot receive this type |
| `422` | `no_agent` | An instruction, and no agent answers the number's messages |
| `422` | `messages_off` | An agent was sent, and the number's messages trigger is off |
| `402` | — | An agent was sent, and the workspace is out of credit |

See [errors](https://docs.talqing.com/api-reference/errors.md) for the envelope.
