Messaging
Conversations, messages, read receipts.
Direct conversations are deduplicated — creating a direct conversation with the same two participants returns the existing row instead of a new one. Participation is enforced on every read / write.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /client/messaging/conversations | Create or return an existing direct conversation. |
| GET | /client/messaging/conversations | Caller's conversations with participant list. |
| POST | /client/messaging/conversations/:conversationId/messages | Send a message (optional thread parent + attachments). |
| GET | /client/messaging/conversations/:conversationId/messages | Paginated message list (optional thread filter). |
| POST | /client/messaging/conversations/:conversationId/typing | Broadcast an ephemeral typing signal. |
| POST | /client/messaging/conversations/:conversationId/messages/:messageId/reactions | Add an emoji reaction. |
| DELETE | /client/messaging/conversations/:conversationId/messages/:messageId/reactions | Remove your reaction (?emoji=). |
| GET | /client/messaging/conversations/:conversationId/messages/:messageId/reactions | List per-emoji reaction counts. |
| GET | /client/messaging/conversations/:conversationId/messages/:messageId/receipts | Who has read this message (seen by N of M). |
| POST | /client/messaging/conversations/:conversationId/participants | Add another user to the conversation. |
| POST | /client/messaging/conversations/:conversationId/read | Update last_read_at. |
POST /client/messaging/conversations
Direct conversations with one participant_ids entry are deduplicated.
Request (CreateConversationInput)
| Field | Type | Required | Default |
|---|---|---|---|
type | "direct" | "group" | no | "direct" |
name | string | no | null |
participant_ids | uuid[] | yes | — |
The caller is auto-added to participant_ids — no need to include yourself.
Response 201 (created) / 200 (existing direct)
Errors
500 CREATE_FAILED.
Try it:
/client/messaging/conversationscurl -X POST 'https://api.amba.dev/v1/client/messaging/conversations'Curl:
GET /client/messaging/conversations
Caller's conversations, newest activity first, with a conversation_participants array that includes each participant's display info.
Query
| Param | Default |
|---|---|
limit | 20 |
offset | 0 |
Response 200
Try it:
/client/messaging/conversationscurl -X GET 'https://api.amba.dev/v1/client/messaging/conversations'Curl:
POST /client/messaging/conversations/:conversationId/messages
Request (SendMessageInput)
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
body | string | yes | — | Non-empty. |
message_type | string | no | "text" | |
metadata | object | no | {} | |
parent_message_id | uuid | no | null | Threaded reply — must reference a message in this conversation. |
attachments | array | no | [] | Each entry { url, content_type?, size_bytes?, metadata? }; url is required. |
Response 201
Errors
400 INVALID_PARENT—parent_message_idisn't a valid UUID.400 INVALID_ATTACHMENTS—attachmentsisn't an array, or an entry is missing a non-emptyurl.403 NOT_A_PARTICIPANT.404 CONVERSATION_NOT_FOUND— conversation doesn't exist.404 PARENT_NOT_FOUND—parent_message_iddoesn't reference a message in this conversation.500 SEND_FAILED.
Try it:
/client/messaging/conversations/%7B%7BconversationId%7D%7D/messagescurl -X POST 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages'Curl:
GET /client/messaging/conversations/:conversationId/messages
Messages in the conversation with sender info, newest first. Each message comes back
enriched with its reactions summary, attachments, and a reply_count.
Query
| Param | Default | Description |
|---|---|---|
limit | 50 | Page size. |
offset | 0 | Offset. |
parent_message_id | — | Optional. List only the replies threaded under this message id. Omit for all. |
Response 200
reactions[].reacted is whether the requesting user reacted with that emoji.
reply_count is the number of messages that thread off this one.
Errors
400 INVALID_PARENT—parent_message_idpresent but not a valid UUID.403 NOT_A_PARTICIPANT.
Try it:
/client/messaging/conversations/%7B%7BconversationId%7D%7D/messagescurl -X GET 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages'Curl:
POST /client/messaging/conversations/:conversationId/typing
Broadcast an ephemeral "this user is typing" signal to the other participants over the live connection. Nothing is persisted — it's fire-and-forget. Membership-gated like send / list.
Response 202
Errors
400 INVALID_CONVERSATION_ID— malformed conversation id.403 NOT_A_PARTICIPANT.500 TYPING_FAILED.
Curl:
Reactions
Add an emoji reaction to a message, remove your own, or list per-emoji counts. Adding is idempotent — re-adding the same emoji is a no-op. Reaction summaries also come back inline on each message in the list response (see above). All three require the caller to be a participant.
| Method | Path | Body / Query |
|---|---|---|
| POST | /client/messaging/conversations/:conversationId/messages/:messageId/reactions | { "emoji": "🔥" } |
| DELETE | /client/messaging/conversations/:conversationId/messages/:messageId/reactions | ?emoji=🔥 |
| GET | /client/messaging/conversations/:conversationId/messages/:messageId/reactions | — |
Errors
403 NOT_A_PARTICIPANT— caller isn't in the conversation.404 MESSAGE_NOT_FOUND— the message isn't in this conversation.
Curl:
POST /client/messaging/conversations/:conversationId/participants
Add another user to an existing conversation. The caller must already be a participant — this is the peer-invite path. The add is idempotent: re-adding an existing member returns the existing participant row with already_member: true instead of an error. Membership can grow over time without recreating the conversation, so a conversation can start with two people and expand into a larger circle.
Request
| Field | Type | Required | Notes |
|---|---|---|---|
user_id | uuid | yes | The user to add. app_user_id accepted. |
Response 200
Errors
400 INVALID_CONVERSATION_ID— malformed conversation id.400 INVALID_PAYLOAD— missing or malformeduser_id.403 NOT_A_PARTICIPANT— caller is not a member of the conversation.404 CONVERSATION_NOT_FOUND— conversation does not exist.404 USER_NOT_FOUND—user_idis not a registered user.
Try it:
/client/messaging/conversations/%7B%7BconversationId%7D%7D/participantscurl -X POST 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/participants'Curl:
SDK
POST /client/messaging/conversations/:conversationId/read
Mark every message in the conversation as read by the caller (stamps last_read_at on the caller's participant record).
Response 200
The updated participant record.
Errors
404 UPDATE_FAILED— caller is not a participant.500 UPDATE_FAILED.
Try it:
/client/messaging/conversations/%7B%7BconversationId%7D%7D/readcurl -X POST 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/read'Curl:
GET /client/messaging/conversations/:conversationId/messages/:messageId/receipts
Read receipts for a single message — who has seen it, for a "seen by N of M"
UI. Derived from each participant's last_read_at (advanced by
POST …/read): a participant has read the message when their last_read_at
is at or after the message's created_at. No extra write path or table —
read state is already tracked per participant. The message's own sender is
excluded from both the list and the recipient total.
Response 200
read_by is ordered by read_at ascending. read_count is its length;
total_recipients is every participant except the sender.
Errors
400 INVALID_ID—conversationIdormessageIdis not a valid UUID.403 NOT_A_PARTICIPANT— caller is not in the conversation.404— conversation or message not found.500 FETCH_FAILED.
Try it:
/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages/%7B%7BmessageId%7D%7D/receiptscurl -X GET 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages/%7B%7BmessageId%7D%7D/receipts'Curl: