/api/public/v1/conversations/{conversationId}/messages/{messageId}/repliesRead a thread
A thread: the root message followed by its replies, oldest first, with the caller’s own thread read marker. `messageId` must be a top-level message of the conversation in the URL. An id from another conversation, or a reply’s id, is a 404 — the same answer as one that does not exist — so a guessed id cannot reach into a conversation the caller was not let into. The conversation is checked first, by the same rule as reading its messages. `root.unreadReplyCount` is how many replies from other people the caller has not seen; `lastReadAt` is the thread’s marker, null until it has been opened or marked read. Reading a thread does not mark it read — that is its own call.
Required scope
The access token must carry this scope. A token missing it gets 403 Forbidden. Grant them to an API client under Dev Tools → API, to an assistant or CLI sign-in when you approve or edit it under Dev Tools, and to a teammate under Employee → Permissions.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
conversationId | string | Required | Conversation ID. |
messageId | string | Required | The thread’s root message — a top-level message of that conversation. |
curl -X GET "https://www.pxb.app/api/public/v1/conversations/{conversationId}/messages/{messageId}/replies" \
-H "Authorization: Bearer your_access_token"{
"message": "Thread retrieved successfully!",
"content": {
"root": {
"id": "msg_9931",
"conversationId": "cnv_5150",
"authorEmployeeId": "emp_12ab",
"parentMessageId": null,
"content": "Deploying the billing export now — <@emp_44f1> heads up.",
"type": "TEXT",
"replyCount": 2,
"lastReplyAt": "2026-08-29T18:22:00.000Z",
"unreadReplyCount": 1,
"editedAt": null,
"deletedAt": null,
"pinnedAt": null,
"attachments": [],
"reactions": [],
"mentions": [
{
"employeeId": "emp_44f1"
}
],
"createdAt": "2026-08-29T18:10:00.000Z"
},
"replies": [
{
"id": "msg_9934",
"conversationId": "cnv_5150",
"authorEmployeeId": "emp_44f1",
"parentMessageId": "msg_9931",
"content": "Seen — holding the other deploy until it lands.",
"type": "TEXT",
"replyCount": 0,
"lastReplyAt": null,
"unreadReplyCount": 0,
"editedAt": null,
"deletedAt": null,
"pinnedAt": null,
"attachments": [],
"reactions": [],
"mentions": [],
"createdAt": "2026-08-29T18:22:00.000Z"
}
],
"lastReadAt": "2026-08-29T18:15:00.000Z"
}
}{
"message": "Message not found."
}In the console
The same thing done by hand, for anyone comparing the API against the screen they already use.