Replying to customers

Pre-release v1, published 2026-09-14. This page commits to the shape of the API, not to a date. We will build exactly what is documented here. The shape can still change until 2026-10-14; after that, changes follow Versioning and stability.

The banner comes off one page at a time as each route goes live. While it is here, build against the contract and assume the route is not callable yet.

Your operators answer end customers from your panel, not ours. A conversation arrives on your webhook, your agent types a reply, and you post it back through this API. The message reaches the customer on whichever channel the conversation belongs to, so the same call covers Instagram and Telegram.

Send a text reply

Every event carries a conversation_id. That is all you need to reply.

curl -X POST https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
"external_id": "op_reply_5521"
}'
Response
{
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_9wY7zA",
"external_id": "op_reply_5521",
"role": "operator",
"type": "text",
"text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
"attachments": [],
"status": "sent",
"created_at": "2026-09-20T11:33:05Z"
}

external_id is your idempotency key. Send the same one twice and you get the original message back rather than a duplicate, so retrying after a timeout is safe.

A status of queued means the channel is rate limited and we will deliver the message when capacity frees. Nothing is discarded.

Send a file or a voice note

Two steps: upload the file, then send a message referencing it.

1

Upload the file

POST /v1/attachments as multipart/form-data. You get back an id.

2

Send the message

Pass that id as attachment_ids. Text is optional when you send a file.

It is two steps rather than one so that a large upload failing mid-transfer can be retried on its own, and so one file can be sent to several conversations without uploading it again.

1. Upload

curl -X POST https://api-chat.parstechai.com/v1/attachments \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx" \
-F "file=@invoice-1042.pdf"
Response
{
"id": "att_5kR2nP",
"type": "document",
"mime": "application/pdf",
"filename": "invoice-1042.pdf",
"size_bytes": 184320,
"expires_at": "2026-09-21T11:35:00Z"
}

2. Send it

curl -X POST https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "attachment_ids": ["att_5kR2nP"], "external_id": "op_invoice_77" }'
Response
{
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_1xZ8bC",
"external_id": "op_invoice_77",
"role": "operator",
"type": "document",
"text": "",
"attachments": [
{
"type": "document",
"url": "https://cdn.parstechai.com/m/1xZ8bC.pdf",
"mime": "application/pdf",
"filename": "invoice-1042.pdf",
"size_bytes": 184320
}
],
"status": "sent",
"created_at": "2026-09-20T11:35:41Z"
}
Supportedimage · video · voice · document
Size limit25 MB per file
Unsent uploadsDiscarded after 24 hours

A voice note is just an attachment with type: voice. Upload the audio file the same way; we infer the type from the media type, or you can pass type=voice explicitly.

Show a typing indicator

Call this when your agent starts typing, so the customer sees the same cue they would in any chat app.

curl -X POST https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/typing \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "is_typing": true }'

The indicator expires by itself after a few seconds. Send it again while the agent keeps typing rather than sending a stop.

Build an operator queue

Filter conversations by status to find the ones waiting for a human.

curl "https://api-chat.parstechai.com/v1/conversations?status=operator_attention" \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx"
Response
{
"data": [
{
"id": "cnv_4dR8nW",
"robot_id": "rbt_8fK2mQ",
"channel_id": "chn_3pQ7xL",
"status": "operator_attention",
"client": { "id": "iguser_88213", "username": "sara.k" },
"last_message_at": "2026-09-20T11:20:44Z",
"unread_count": 2,
"created_at": "2026-09-20T11:08:59Z"
}
],
"pagination": { "limit": 50, "offset": 0, "total_count": 1, "has_more": false }
}

You do not have to poll this. The chat webhook event fires the moment a conversation enters operator_attention, so use the event to update your queue and this endpoint to rebuild it after a restart.

Close a conversation

curl -X POST https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/close \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx"

Closing is a context boundary, not a deletion. The transcript stays and remains readable. What changes is that the next conversation with that customer starts without the earlier history as AI context. See context_reset.