Skip to navigation

Replying to customers

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. With several files, the text is sent once, as the caption of the first.

It is two steps rather than one so that a large upload failing mid-transfer can be retried on its own, without re-sending the message.

conversation_id is required at upload. An attachment belongs to the channel it will be sent on, so we need the destination before the file can be stored. One upload serves one conversation — to send the same file to two conversations, upload it twice.

1. Upload

curl -X POST https://api-chat.parstechai.com/v1/attachments \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx" \
-F "conversation_id=cnv_4dR8nW" \
-F "file=@product-photo.jpg"
Response
{
"id": "att_5kR2nP",
"identifier": "9f2c41b8e7d4",
"name": "product-photo.jpg",
"url": "https://cdn.parstechai.com/a/9f2c41b8e7d4.jpg",
"type": "image"
}

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_photo_77" }'
Response
{
"conversation_id": "cnv_4dR8nW",
"message_id": "msg_1xZ8bC",
"external_id": "op_photo_77",
"role": "operator",
"type": "image",
"text": "",
"attachments": [],
"status": "sent",
"created_at": "2026-09-20T11:35:41Z"
}

attachments comes back empty on the send response, even when the message carried one. Use the id you got from the upload rather than expecting it echoed here. The file is on the message — the message webhook event delivers it with its attachments populated.

Size is a plan dimension, checked per file at upload:

KindBasicProfessionalAccepted types
Image2 MB4 MBimage/jpeg · image/png
Video10 MB20 MBvideo/mp4 · video/quicktime (.mov) · video/webm · video/ogg · video/x-msvideo (.avi)
Voice / audio5 MB10 MBaudio/aac · audio/mp4 · audio/x-m4a · audio/wav
Document5 MB10 MBapplication/pdf

The usual alternative spellings are accepted too (image/jpg, video/avi, audio/m4a, audio/wave, audio/x-wav, audio/vnd.wave). This is exactly what Instagram delivers: GIF, WebP and other formats are refused at upload, rather than accepted and then failing when you send.

Every plan with API access sends all four types — the difference between tiers is size, not which kinds of file you may send. If your agreement sets different caps, those apply instead.

A file type outside this list, or a file over your plan’s cap, is rejected with 422 validation_failed before it is stored; for the size cap, details carries kind, cap_mb and size_bytes. A kind of file your plan does not include is 403 attachment_not_in_plan. Unsent uploads are discarded after 24 hours.

Full limits, and how they relate to Instagram’s own, are on Rate limits.

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 }
}

unread_count is the number of customer messages since the last reply from an operator, the AI or an agent: what is still waiting for an answer.

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.