Conversations
List, fetch, and mark-as-read message conversations.
A conversation is the running thread between one of your channels and one recipient phone number. It's created automatically the first time a message flows in either direction. You don't create conversations directly — these endpoints are for reading and managing them.
List conversations
GET /v1/messaging/conversationsQuery parameters
| Parameter | Type | Description |
|---|---|---|
channel_id | uuid | Restrict to one channel. |
status | string | open or closed. Omit to return both. |
category | string | Restrict to one category (e.g. service). |
search | string | Matches contact name, contact phone number, or the last message text. |
page | integer | 1-indexed. Default: 1. |
per_page | integer | Default: 20, max: 100. |
There's no unread_only filter — filter client-side on unread_count > 0 after fetching, or use search/status to narrow the list first.
Example
curl -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
"https://api.tryvox.io/v1/messaging/conversations?status=open&per_page=50"Response
200 OK:
{
"data": [
{
"id": "3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"account_id": "TJab12cd34",
"channel_id": "8f7a4b2e-91c2-4f3d-9a1e-2c4b6d8f0a1c",
"contact_id": "4d5e6f70-8192-a3b4-c5d6-e7f8a9b0c1d2",
"contact": {
"id": "4d5e6f70-8192-a3b4-c5d6-e7f8a9b0c1d2",
"name": "Alex Carter",
"phone_number": "+14155551234"
},
"category": "service",
"status": "open",
"last_message_at": "2026-04-09T11:14:32Z",
"last_message_preview": "Yes, please go ahead.",
"unread_count": 2,
"created_at": "2026-04-09T09:00:00Z",
"updated_at": "2026-04-09T11:14:32Z"
}
],
"meta": {"page": 1, "per_page": 50, "total": 1}
}The recipient's phone number is contact.phone_number (nested), not a top-level contact_phone field. expires_at, when present, is when the 24-hour customer service window closes — check it before sending plain text/media; past it, TryVox returns 403 OUTSIDE_CS_WINDOW and you need a template instead.
Get a conversation
GET /v1/messaging/conversations/{id}curl -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
https://api.tryvox.io/v1/messaging/conversations/3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8fReturns the same shape as a list entry — no extra message history attached. Use Get messages for that.
Get messages in a conversation
GET /v1/messaging/conversations/{id}/messagesQuery parameters
| Parameter | Type | Description |
|---|---|---|
before | uuid | Return messages older than this message_id (cursor for paging back). |
limit | integer | Max messages. Default: 50, max: 100. |
Example
curl -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
"https://api.tryvox.io/v1/messaging/conversations/$CONV_ID/messages?limit=50"Response
200 OK:
{
"data": [
{
"id": "2c3d4e5f-6071-8293-a4b5-c6d7e8f9a0b1",
"conversation_id": "3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"contact_id": "4d5e6f70-8192-a3b4-c5d6-e7f8a9b0c1d2",
"direction": "inbound",
"message_type": "text",
"content": "Thanks! Confirmed.",
"status": "received",
"has_media": false,
"timestamp": "2026-04-09T11:14:32Z",
"created_at": "2026-04-09T11:14:32Z"
},
{
"id": "1b2c3d4e-5f60-7180-9a2b-3c4d5e6f7a8b",
"conversation_id": "3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"contact_id": "4d5e6f70-8192-a3b4-c5d6-e7f8a9b0c1d2",
"direction": "outbound",
"message_type": "text",
"content": "Hi, your appointment is confirmed for tomorrow at 10am.",
"status": "delivered",
"has_media": false,
"timestamp": "2026-04-09T09:00:12Z",
"created_at": "2026-04-09T09:00:12Z"
}
]
}Messages are returned newest-first (matching the prose above — the inbound reply at 11:14:32 comes before the outbound message from 09:00:12). Page back in time with ?before={oldest_id_you_have}. There's no has_more flag — if you get back exactly limit rows, assume there may be more and page again; fewer than limit means you've reached the start of the conversation.
There's no to/from field on a message — the recipient/sender phone number is the conversation's contact_phone (Get a conversation), not repeated on every message. For inbound media (message_type is image, video, audio, or document), has_media is true and media_id/media_mime_type are set — see Download inbound media below. For a button or list reply, message_type is interactive, content is a human-readable summary (e.g. the button's label), and interactive_payload carries the full structured reply if you need to branch on it programmatically.
Download inbound media
When a customer sends an image, video, audio note, or document, TryVox downloads it from Meta and stores it; the message row has has_media: true and a media_id. Fetch the bytes with:
GET /v1/messaging/messages/{message_id}/mediacurl -u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
https://api.tryvox.io/v1/messaging/messages/$MESSAGE_ID/media \
-o received-fileThis streams the raw file, not JSON — the response Content-Type is the media's real MIME type (from media_mime_type on the message), and it supports HTTP Range requests for partial/resumable downloads. 404 MEDIA_NOT_FOUND means TryVox hasn't successfully downloaded the file from Meta yet (or the download failed) — call POST /v1/messaging/messages/{message_id}/media/retry-download to force another attempt. Meta eventually expires media it hasn't served, so a very old undelivered file may never succeed — treat repeated retry failures as permanent.
Mark as read
Marks every unread inbound message in the conversation as read. Outbound messages are unaffected.
POST /v1/messaging/conversations/{id}/readcurl -X POST \
-u $TRYVOX_AUTH_ID:$TRYVOX_AUTH_TOKEN \
https://api.tryvox.io/v1/messaging/conversations/$CONV_ID/read204 No Content. The conversation's unread_count resets to zero in TryVox. This does not send a read receipt to Meta/WhatsApp — it only affects your own unread count, not whether the sender sees blue ticks.
Errors
| Status | Code | Reason |
|---|---|---|
| 401 | INVALID_CREDENTIALS | Auth ID / Auth Token bad |
| 404 | CONVERSATION_NOT_FOUND | No conversation with this id on this tenant |
Notes
- No "create conversation" endpoint. Conversations exist as a side effect of messages. To start a thread, send a template message via Send a Message.
- Status is auto-managed. Conversations close after 30 days of inactivity (
status: "closed"). A new inbound or outbound message on a closed conversation reopens it. - No archive endpoint. Use
status=closedfilter to see archived threads.