Amba

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

MethodPathDescription
POST/client/messaging/conversationsCreate or return an existing direct conversation.
GET/client/messaging/conversationsCaller's conversations with participant list.
POST/client/messaging/conversations/:conversationId/messagesSend a message (optional thread parent + attachments).
GET/client/messaging/conversations/:conversationId/messagesPaginated message list (optional thread filter).
POST/client/messaging/conversations/:conversationId/typingBroadcast an ephemeral typing signal.
POST/client/messaging/conversations/:conversationId/messages/:messageId/reactionsAdd an emoji reaction.
DELETE/client/messaging/conversations/:conversationId/messages/:messageId/reactionsRemove your reaction (?emoji=).
GET/client/messaging/conversations/:conversationId/messages/:messageId/reactionsList per-emoji reaction counts.
GET/client/messaging/conversations/:conversationId/messages/:messageId/receiptsWho has read this message (seen by N of M).
POST/client/messaging/conversations/:conversationId/participantsAdd another user to the conversation.
POST/client/messaging/conversations/:conversationId/readUpdate last_read_at.

POST /client/messaging/conversations

Direct conversations with one participant_ids entry are deduplicated.

Request (CreateConversationInput)

FieldTypeRequiredDefault
type"direct" | "group"no"direct"
namestringnonull
participant_idsuuid[]yes

The caller is auto-added to participant_ids — no need to include yourself.

Response 201 (created) / 200 (existing direct)

{ "data": { "id": "…", "type": "direct", "name": null, "created_at": "…" } }

Errors

  • 500 CREATE_FAILED.

Try it:

POST/client/messaging/conversations
client auth
curl -X POST 'https://api.amba.dev/v1/client/messaging/conversations'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/client/messaging/conversations' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /client/messaging/conversations

Caller's conversations, newest activity first, with a conversation_participants array that includes each participant's display info.

Query

ParamDefault
limit20
offset0

Response 200

{
  "data": [
    {
      "id": "…",
      "type": "direct",
      "conversation_participants": [
        { "app_user_id": "…", "app_users": { "id": "…", "display_name": "…", "avatar_url": "…" } }
      ]
    }
  ],
  "total": 10,
  "offset": 0,
  "limit": 20
}

Try it:

GET/client/messaging/conversations
client auth
curl -X GET 'https://api.amba.dev/v1/client/messaging/conversations'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/client/messaging/conversations' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}'

POST /client/messaging/conversations/:conversationId/messages

Request (SendMessageInput)

FieldTypeRequiredDefaultNotes
bodystringyesNon-empty.
message_typestringno"text"
metadataobjectno{}
parent_message_iduuidnonullThreaded reply — must reference a message in this conversation.
attachmentsarrayno[]Each entry { url, content_type?, size_bytes?, metadata? }; url is required.

Response 201

{
  "data": {
    "id": "…",
    "conversation_id": "…",
    "sender_id": "…",
    "body": "…",
    "message_type": "text",
    "parent_message_id": null,
    "attachments": [
      {
        "id": "…",
        "url": "https://…/clip.mp4",
        "content_type": "video/mp4",
        "size_bytes": 184320,
        "metadata": {}
      }
    ],
    "created_at": "…"
  }
}

Errors

  • 400 INVALID_PARENTparent_message_id isn't a valid UUID.
  • 400 INVALID_ATTACHMENTSattachments isn't an array, or an entry is missing a non-empty url.
  • 403 NOT_A_PARTICIPANT.
  • 404 CONVERSATION_NOT_FOUND — conversation doesn't exist.
  • 404 PARENT_NOT_FOUNDparent_message_id doesn't reference a message in this conversation.
  • 500 SEND_FAILED.

Try it:

POST/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages
client auth
curl -X POST 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/client/messaging/conversations/{conversationId}/messages' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

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

ParamDefaultDescription
limit50Page size.
offset0Offset.
parent_message_idOptional. List only the replies threaded under this message id. Omit for all.

Response 200

{
  "data": [
    {
      "id": "…",
      "body": "…",
      "created_at": "…",
      "parent_message_id": null,
      "sender": { "id": "…", "display_name": "…", "avatar_url": "…" },
      "reactions": [{ "emoji": "🔥", "count": 3, "reacted": true }],
      "attachments": [
        {
          "id": "…",
          "url": "https://…",
          "content_type": "image/png",
          "size_bytes": 2048,
          "metadata": {}
        }
      ],
      "reply_count": 2
    }
  ],
  "total": 100,
  "offset": 0,
  "limit": 50
}

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_PARENTparent_message_id present but not a valid UUID.
  • 403 NOT_A_PARTICIPANT.

Try it:

GET/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages
client auth
curl -X GET 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/client/messaging/conversations/{conversationId}/messages' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}'

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

{ "data": { "accepted": true } }

Errors

  • 400 INVALID_CONVERSATION_ID — malformed conversation id.
  • 403 NOT_A_PARTICIPANT.
  • 500 TYPING_FAILED.

Curl:

curl -X POST '${BASE_URL}/client/messaging/conversations/{conversationId}/typing' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}'

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.

MethodPathBody / 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:

curl -X POST '${BASE_URL}/client/messaging/conversations/{conversationId}/messages/{messageId}/reactions' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{ "emoji": "🔥" }'

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

FieldTypeRequiredNotes
user_iduuidyesThe user to add. app_user_id accepted.

Response 200

{
  "data": {
    "id": "…",
    "conversation_id": "…",
    "app_user_id": "…",
    "joined_at": "…",
    "last_read_at": null
  },
  "already_member": false
}

Errors

  • 400 INVALID_CONVERSATION_ID — malformed conversation id.
  • 400 INVALID_PAYLOAD — missing or malformed user_id.
  • 403 NOT_A_PARTICIPANT — caller is not a member of the conversation.
  • 404 CONVERSATION_NOT_FOUND — conversation does not exist.
  • 404 USER_NOT_FOUNDuser_id is not a registered user.

Try it:

POST/client/messaging/conversations/%7B%7BconversationId%7D%7D/participants
client auth
curl -X POST 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/participants'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/client/messaging/conversations/{conversationId}/participants' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{ "user_id": "…" }'

SDK

// The caller must already be a participant in the conversation.
await amba.messaging.addParticipant(conversationId, userId);

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:

POST/client/messaging/conversations/%7B%7BconversationId%7D%7D/read
client auth
curl -X POST 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/read'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/client/messaging/conversations/{conversationId}/read' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

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

{
  "data": {
    "read_by": [{ "app_user_id": "…", "read_at": "…" }],
    "read_count": 1,
    "total_recipients": 3
  }
}

read_by is ordered by read_at ascending. read_count is its length; total_recipients is every participant except the sender.

Errors

  • 400 INVALID_IDconversationId or messageId is 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:

GET/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages/%7B%7BmessageId%7D%7D/receipts
client auth
curl -X GET 'https://api.amba.dev/v1/client/messaging/conversations/%7B%7BconversationId%7D%7D/messages/%7B%7BmessageId%7D%7D/receipts'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/client/messaging/conversations/{conversationId}/messages/{messageId}/receipts' \
  -H 'X-Api-Key: ${CLIENT_API_KEY}' \
  -H 'Authorization: Bearer ${SESSION_TOKEN}'