Messaging and calls
Rooms, messages, attachments, polls, scheduled messages, guest invitations, and call recording and intelligence.
57 endpoints· 10 called live· 5 not exercised here· verified against http://127.0.0.1:8741
57 endpoints
GET/api/v1/messaging/attachments/{attachment_id}/fileVerified in sessionDownload a message attachment, if the caller is a member of its room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| attachment_id | string | required | Attachment ID |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/attachments/{attachment_id}/file' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
FileResponse of the stored attachment.
Errors
| Status | detail |
|---|---|
| 404 | Attachment not found |
| 404 | Attachment file not found |
| 500 | Internal server error |
GET/api/v1/messaging/bookmarksCalled liveThe caller's saved messages.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | optional | Default 100. max 300. |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/bookmarks' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"bookmarks": [
"<empty list>"
]
}Errors
| Status | detail |
|---|---|
| 500 | Failed to list saved messages |
GET/api/v1/messaging/calls/{call_id}/intelligenceKnown unprovenOne read for the whole post-call record.
One read for the whole post-call record. `transcript` is null when nothing was ever recorded — which the UI must render as "not recorded", not as a failure. When a recording exists but no key does, `transcript.status` is 'unavailable' and `transcript.error` is the sentence explaining exactly which setting is missing.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| call_id | string | required | Call ID |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/calls/{call_id}/intelligence' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"call_id": "…",
"duration_seconds": "…",
"mode": "…",
"recorded": "…",
"recording": "…",
"room_id": "…",
"room_name": "…",
"transcript": "…"
}Errors
| Status | detail |
|---|---|
| 503 | Database is unavailable |
POST/api/v1/messaging/calls/{call_id}/recording/chunksKnown unprovenAppend one MediaRecorder blob to the in-progress upload.
Append one MediaRecorder blob to the in-progress upload. Chunks arrive DURING the call, not after: a dropped tab at minute 58 then costs 58 seconds of audio instead of 58 minutes. The file is opened in append mode, so an index that already landed must be a no-op rather than a second append — appending it twice puts duplicate frames in the middle of the container and every decoder reads garbage from there on. Replaying it as a no-op (not a 4xx) is deliberate: the case that actually happens is the server receiving a chunk and the RESPONSE being lost, and answering the retry with an error would make the recorder discard a recording whose bytes are already safely on disk.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| call_id | string | required | Call ID |
Request body (required) — Body_upload_recording_chunk_api_v1_messaging_calls__call_id__recording_chunks_post
| Field | Type | Required | Notes |
|---|---|---|---|
| upload_id | string | required | Client-minted uuid4 hex for this upload |
| chunk_index | integer | required | |
| chunk | string | required |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/calls/{call_id}/recording/chunks' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "upload_id": "<string>", "chunk_index": 0, "chunk": "<string>" }'Response · 200
{
"bytes_received": "…",
"chunk_index": "…",
"duplicate": "…",
"upload_id": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Empty chunk |
| 413 | Chunk is too large |
| 413 | Recording exceeds the maximum size |
POST/api/v1/messaging/calls/{call_id}/recording/finalizeKnown unprovenSeal the upload into a recording row — IF it was ever announced.
Seal the upload into a recording row — IF it was ever announced. This is the consent gate, and it is a hard one: `announcement` comes from messaging_call.metadata->'recordings', which only calls.py::announce_recording writes, and that function is the same code path that broadcast the indicator to every peer, rang every participant's lobby socket, and posted the "started recording" system message. A client that recorded silently has no announcement to point at and gets a 409. There is no override.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| call_id | string | required | Call ID |
Request body (required) — FinalizeRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| upload_id | string | required | |
| mime | string | optional | Default "audio/webm". |
| duration_seconds | integer | optional | Default 0. |
| consent_acks | object[]? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/calls/{call_id}/recording/finalize' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "upload_id": "<string>", "mime": "audio/webm", "duration_seconds": 0, "consent_acks": [ "<object>" ] }'Response · 200
{
"bytes": "…",
"call_id": "…",
"consent_shown_to": "…",
"duration_seconds": "…",
"mime": "…",
"recording_id": "…",
"room_id": "…",
"transcript_status": "…"
}Errors
| Status | detail |
|---|---|
| 404 | No uploaded chunks for that upload id |
| 409 | This recording was never announced to the call's participants, so it cannot be stored. Recording must start with call-recording-start, which shows every participant a recording indicator. |
| 403 | Only the participant who announced this recording can upload it. |
| 415 | Only audio or video recordings are accepted |
| 503 | Database is unavailable |
GET/api/v1/messaging/calls/{call_id}/recordings/{recording_id}/fileKnown unprovenMembership-checked stream.
Membership-checked stream. Never a static mount — a recording is the most sensitive artifact this product holds, and an unauthenticated URL for it is a breach waiting for a search engine.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| call_id | string | required | |
| recording_id | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/calls/{call_id}/recordings/{recording_id}/file' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
FileResponse of the stored recording (media type from the row, default audio/webm).
Errors
| Status | detail |
|---|---|
| 503 | Database is unavailable |
| 404 | Recording not found |
| 404 | Recording file is missing from storage |
POST/api/v1/messaging/calls/{call_id}/transcribeKnown unprovenFor the common case: the recording landed before anyone configured a key.
For the common case: the recording landed before anyone configured a key. Idempotent — run_intelligence upserts on recording_id, so this replaces the 'unavailable' row rather than stacking a second transcript beside it.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| call_id | string | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/calls/{call_id}/transcribe' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"call_id": "…",
"recording_id": "…",
"status": "…"
}Errors
| Status | detail |
|---|---|
| 503 | Database is unavailable |
| 404 | This call has no stored recording |
GET/api/v1/messaging/calls/activeCalled liveDrives the rooms-rail 'live' pills on first paint, before the lobby socket is up.
Drives the rooms-rail 'live' pills on first paint, before the lobby socket is up.
Request
curl 'https://api.vxcloud.io/api/v1/messaging/calls/active' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"calls": [
"<empty list>"
],
"count": "int"
}GET/api/v1/messaging/chat-invitations/{token}url-secret-onlyVerified in sessionPublic lookup of a guest invitation by token.
The guest invitation token is the credential.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| token | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/chat-invitations/{token}'Response · 200
{
"display_name": "…",
"email": "…",
"expired": "…",
"expires_at": "…",
"invited_by": "…",
"message": "…",
"organization_name": "…",
"room_name": "…",
"status": "…",
"token": "…"
}Errors
| Status | detail |
|---|---|
| 503 | Database unavailable |
| 404 | Invitation not found |
POST/api/v1/messaging/chat-invitations/{token}/accepturl-secret-onlyVerified in sessionPublic accept of a guest invitation; provisions the guest user and room membership.
The guest invitation token is the credential.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| token | string | required |
Request body (optional) — GuestAcceptRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| display_name | string? | optional | |
| password | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/chat-invitations/{token}/accept' \
-H 'Content-Type: application/json' \
-d '{ "display_name": "<string>", "password": "<string>" }'Response · 200
{
"access": "…",
"display_name": "…",
"email": "…",
"refresh": "…",
"room_id": "…",
"room_name": "…",
"user_id": "…"
}Errors
| Status | detail |
|---|---|
| 503 | Database unavailable |
| 404 | Invitation not found |
| 409 | This invitation was revoked |
| 409 | This invitation was declined |
| 410 | This invitation has expired |
| 409 | This email now belongs to a SalesShift account. Sign in and open the room from your inbox instead. |
POST/api/v1/messaging/chat-invitations/{token}/declineurl-secret-onlyVerified in sessionPublic decline of a guest invitation.
The guest invitation token is the credential.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| token | string | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/chat-invitations/{token}/decline'Response · 200
{
"status": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Invitation not found |
| 409 | This invitation was already accepted |
DELETE/api/v1/messaging/guest-invitations/{invitation_id}Verified in sessionRevoke a guest's access.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| invitation_id | string | required |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/messaging/guest-invitations/{invitation_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"id": "…",
"status": "…"
}Errors
| Status | detail |
|---|---|
| 403 | Access denied |
| 404 | Invitation not found |
GET/api/v1/messaging/healthopenCalled liveLive health for the messaging plane.
Live health for the messaging plane. Reports the realtime path (Postgres + Redis Pub/Sub + WebSocket sessions), the durability path (transactional outbox processor + queue depth), the async fanout (Celery worker reachability), and the analytics fan-out (Kafka producer state). Anything `degraded` is non-fatal — the realtime path keeps working — but should be visible.
Anonymous health check. No credential of any kind.
Request
curl 'https://api.vxcloud.io/api/v1/messaging/health'Response · 200
{
"status": "str",
"services": {
"database": "str",
"redis": "str",
"celery": {
"reachable": "bool",
"workers": "int"
},
"kafka": {
"enabled": "bool",
"available": "bool",
"topic": "null"
},
"outbox": {
"processor_running": "bool",
"pending": "int",
"failed": "int"
}
},
"active_connections": "int",
"timestamp": "str"
}POST/api/v1/messaging/hooks/{token}url-secret-onlyVerified in sessionPost a message into a room from an incoming webhook; the token is the only credential.
The token IS the credential — the route's own summary says so. Tokens shorter than 32 characters are rejected.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| token | string | required |
Request body (required) — WebhookPost
| Field | Type | Required | Notes |
|---|---|---|---|
| text | string | required | |
| username | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/hooks/{token}' \
-H 'Content-Type: application/json' \
-d '{ "text": "<string>", "username": "<string>" }'Response · 200
{
"message_id": "…",
"ok": "…"
}Errors
| Status | detail |
|---|---|
| 422 | text required |
| 404 | Unknown webhook |
| 500 | Failed to post webhook message |
| 500 | Webhook lookup failed |
GET/api/v1/messaging/invitationsCalled liveThe caller's pending room invitations.
Request
curl 'https://api.vxcloud.io/api/v1/messaging/invitations' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"invitations": [
"<empty list>"
]
}POST/api/v1/messaging/invitations/{invitation_id}/respondVerified in sessionAccept or decline a room invitation.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| invitation_id | string | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| accept | boolean | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/invitations/{invitation_id}/respond?accept=<boolean>' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"id": "…",
"room_id": "…",
"status": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Invitation not found |
| 500 | Failed to respond to invitation |
| 409 | computed at runtime: 'Invitation already %s' % inv['status'] |
GET/api/v1/messaging/mentionsCalled liveThe caller's mention inbox.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| unread_only | boolean | optional | Default false. |
| limit | integer | optional | Default 50. max 200. |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/mentions' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"mentions": [
{
"id": "str",
"is_read": "bool",
"message_id": "str",
"room_id": "str",
"room_name": "str",
"sender_id": "int",
"content": "str",
"created_at": "str"
},
"...x2"
]
}Errors
| Status | detail |
|---|---|
| 400 | Invalid user |
| 500 | Failed to list mentions |
POST/api/v1/messaging/mentions/readVerified in sessionMark a set of mentions read.
Request body (required) — MentionsReadRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| message_ids | string[] | required |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/mentions/read' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "message_ids": [ "<string>" ] }'Response · 200
{
"updated": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Failed to mark mentions read |
POST/api/v1/messaging/messages/{message_id}/ackVerified in sessionAcknowledge a priority message.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| message_id | string | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/messages/{message_id}/ack' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
The acknowledgement payload object.
Errors
| Status | detail |
|---|---|
| 404 | Message not found |
| 500 | Failed to acknowledge |
GET/api/v1/messaging/messages/{message_id}/acksVerified in sessionWho has acknowledged a message.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| message_id | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/messages/{message_id}/acks' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"acks": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Message not found |
POST/api/v1/messaging/messages/{message_id}/bookmarkVerified in sessionSave a message to the caller's bookmarks.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| message_id | string | required |
Request body (required) — BookmarkRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| note | string? | optional | |
| tags | string[]? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/messages/{message_id}/bookmark' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "note": "<string>", "tags": [ "<string>" ] }'Response · 200
{
"message_id": "…",
"saved": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Message not found |
| 500 | Failed to save message |
DELETE/api/v1/messaging/messages/{message_id}/bookmarkVerified in sessionRemove a message from the caller's bookmarks.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| message_id | string | required |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/messaging/messages/{message_id}/bookmark' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"message_id": "…",
"saved": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Failed to unsave message |
POST/api/v1/messaging/polls/{poll_id}/voteVerified in sessionVote on a poll.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| poll_id | string | required |
Request body (required) — PollVoteRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| option_ids | string[] | required |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/polls/{poll_id}/vote' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "option_ids": [ "<string>" ] }'Response · 200
The poll snapshot object after the vote.
Errors
| Status | detail |
|---|---|
| 404 | Poll not found |
| 500 | Failed to vote |
| 409 | Poll is closed |
| 422 | No valid options selected |
GET/api/v1/messaging/roomsCalled liveRooms the caller belongs to; guests see only their own rooms.
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
[
{
"id": "str",
"name": "str",
"description": "str",
"room_type": "str",
"is_active": "bool",
"is_public": "bool",
"is_global": "bool",
"created_at": "str",
"updated_at": "str",
"last_activity": "str",
"settings": "null",
"max_participants": "int",
"created_by_id": "int",
"participant_count": "int",
"participant_ids": [
"str",
"...x2"
],
"other_user_id": "str",
"my_settings": {
"is_muted": "bool",
"is_favorite": "bool",
"added_to_list": "str"
},
"other_user": {
"id": "str",
"name": "str",
"username": "str",
"email": "str",
"role": "str",
"avatar": "null",
"avatar_color": "str",
"organization_id": "str",
"status": "str",
"last_seen": "str"
}
},
"...x19"
]Errors
| Status | detail |
|---|---|
| 500 | Internal server error |
POST/api/v1/messaging/roomsVerified in sessionCreate a chat room; cross-organization rooms are superuser-only and guests may not create rooms.
Request body (required) — CreateRoomRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | required | |
| type | string | optional | Default "private". |
| description | string? | optional | |
| participant_user_ids | string[]? | optional | |
| is_global | boolean | optional | Default false. |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "name": "<string>", "type": "private", "description": "<string>", "participant_user_ids": [ "<string>" ], "is_global": false }'Response · 200
The created room object.
Errors
| Status | detail |
|---|---|
| 500 | Failed to create room |
| 500 | Internal server error |
GET/api/v1/messaging/rooms/{room_id}Verified in sessionOne room, if the caller is a member.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
The room object returned by _require_member.
Errors
| Status | detail |
|---|---|
| 500 | Internal server error |
POST/api/v1/messaging/rooms/{room_id}/aiPartial`mode='ask'`: posts the question, then VxAI's answer, into the room.
`mode='ask'`: posts the question, then VxAI's answer, into the room. `mode='catchup'`: posts an AI summary of the recent conversation. Runs entirely against the LOCAL OpenClaw gateway — no external APIs.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request body (required) — RoomAiRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| prompt | string? | optional | |
| mode | string | optional | Default "ask". |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/ai' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "prompt": "<string>", "mode": "ask" }'Response · 200
{
"mode": "…",
"queued": "…"
}Errors
| Status | detail |
|---|---|
| 422 | prompt required for mode='ask' |
POST/api/v1/messaging/rooms/{room_id}/attachmentsVerified in sessionUpload an attachment and create a chat message that references it.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request body (required) — Body_upload_room_attachment_api_v1_messaging_rooms__room_id__attachments_post
| Field | Type | Required | Notes |
|---|---|---|---|
| file | string | required | |
| caption | string | optional | Default "". |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/attachments' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "file": "<string>", "caption": "" }'Response · 200
{
"attachments": "…",
"content": "…",
"created_at": "…",
"id": "…",
"is_deleted": "…",
"is_edited": "…",
"is_pinned": "…",
"message_type": "…",
"room_id": "…",
"sender_id": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Attachment is empty |
| 413 | Attachment is too large |
| 500 | Failed to send attachment |
| 500 | Internal server error |
GET/api/v1/messaging/rooms/{room_id}/callVerified in sessionThe live call in a room, if there is one.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/call' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"active": "…",
"call": "…"
}GET/api/v1/messaging/rooms/{room_id}/callsVerified in sessionPast calls in a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | optional | Default 50. min 1. max 200. |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/calls' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"calls": "…",
"count": "…"
}GET/api/v1/messaging/rooms/{room_id}/guest-invitationsVerified in sessionGuest invitations issued for a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/guest-invitations' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"invitations": "…"
}Errors
| Status | detail |
|---|---|
| 403 | Access denied |
POST/api/v1/messaging/rooms/{room_id}/guest-invitationsVerified in sessionInvite an external CRM contact into one room as a guest; guests may not invite guests.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room the guest will be confined to |
Request body (required) — GuestInviteRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| contact_id | string? | optional | |
| string? | optional | ||
| name | string? | optional | |
| message | string? | optional | |
| sender_email | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/guest-invitations' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "contact_id": "<string>", "email": "<string>", "name": "<string>", "message": "<string>", "sender_email": "<string>" }'Response · 200
{
"detail": "…",
"display_name": "…",
"email": "…",
"email_error": "…",
"email_sent": "…",
"expires_at": "…",
"guest_user_id": "…",
"id": "…",
"invite_endpoint": "…",
"invite_url": "…",
"mode": "…",
"room_id": "…",
"status": "…",
"user_id": "…"
}Errors
| Status | detail |
|---|---|
| 403 | Guests cannot invite others |
| 400 | Only a user with an organization can invite a guest |
| 400 | A guest cannot be invited into a global room — invite them into a private room instead |
| 503 | Database unavailable |
| 400 | A valid email address is required |
| 404 | Contact not found |
| 400 | That contact has no email address, so there is nowhere to send the invitation |
| 400 | Invalid contact id |
POST/api/v1/messaging/rooms/{room_id}/invitationsVerified in sessionInvite a platform user to a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request body (required) — InviteRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| invited_user_id | integer | required | |
| role | string | optional | Default "member". |
| message | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/invitations' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "invited_user_id": 0, "role": "member", "message": "<string>" }'Response · 200
{
"id": "…",
"invited_user_id": "…",
"room_id": "…",
"status": "…"
}Errors
| Status | detail |
|---|---|
| 409 | User is already a member |
| 500 | Failed to create invitation |
| 404 | User not found |
| 409 | An invitation is already pending |
POST/api/v1/messaging/rooms/{room_id}/joinVerified in sessionJoin a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID to join |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/join' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"message": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Failed to join room |
| 404 | Room not found |
| 500 | Internal server error |
| 403 | Room is not joinable |
POST/api/v1/messaging/rooms/{room_id}/leaveVerified in sessionLeave a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID to leave |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/leave' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"message": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Failed to leave room |
| 404 | Room not found |
| 500 | Internal server error |
GET/api/v1/messaging/rooms/{room_id}/messagesVerified in sessionMessage history for a room, paged backwards from a timestamp.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | optional | Number of messages to retrieve Default 50. |
| before | string? | optional | Get messages before this timestamp |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
A JSON array of message objects (empty array when there are none).
Errors
| Status | detail |
|---|---|
| 500 | Internal server error |
POST/api/v1/messaging/rooms/{room_id}/messagesVerified in sessionSend a message via REST API (fallback when WebSocket is not available)
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request body (required) — SendMessageRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| content | string | required | |
| message_type | MessageType | optional | Default "text". |
| reply_to | string? | optional | |
| thread_root | string? | optional | |
| attachments | object[]? | optional | |
| metadata | object? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "content": "<string>", "message_type": "text", "reply_to": "<string>", "thread_root": "<string>", "attachments": [ "<object>" ], "metadata": {} }'Response · 200
{
"attachments": "…",
"content": "…",
"created_at": "…",
"id": "…",
"is_deleted": "…",
"is_edited": "…",
"is_pinned": "…",
"message_type": "…",
"room_id": "…",
"sender_id": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Failed to send message |
| 500 | Internal server error |
PUT/api/v1/messaging/rooms/{room_id}/messages/{message_id}Verified in sessionEdit your own message.
Edit your own message. Shows an 'edited' marker; no edit history (WhatsApp semantics).
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | |
| message_id | string | required |
Request body (required) — EditMessageRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| content | string | required |
Values in the request below are typed placeholders, not sample data.
Request
curl -X PUT 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages/{message_id}' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "content": "<string>" }'Response · 200
{
"content": "…",
"edited_at": "…",
"id": "…",
"is_edited": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Message not found |
| 403 | You can only edit your own messages |
| 422 | Content cannot be empty |
| 500 | Failed to edit message |
DELETE/api/v1/messaging/rooms/{room_id}/messages/{message_id}Verified in sessionSoft-delete a single message ("delete for everyone").
Soft-delete a single message ("delete for everyone"). Only the original sender or the room creator may delete a message. The row is marked is_deleted=true (retained for audit; filtered out of history) and a `message_deleted` event is broadcast over Redis + the in-memory socket manager so every connected client removes the bubble live.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
| message_id | string | required | Message ID |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages/{message_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"id": "…",
"is_deleted": "…",
"room_id": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Room not found |
| 404 | Message not found |
| 403 | You can only delete your own messages |
| 500 | Failed to delete message |
| 500 | Internal server error |
POST/api/v1/messaging/rooms/{room_id}/messages/{message_id}/forwardVerified in sessionForward to up to 5 rooms the caller belongs to (WhatsApp's anti-spam cap).
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | |
| message_id | string | required |
Request body (required) — ForwardRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| target_room_ids | string[] | required |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages/{message_id}/forward' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "target_room_ids": [ "<string>" ] }'Response · 200
{
"count": "…",
"forwarded": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Message not found |
| 422 | No valid target rooms |
POST/api/v1/messaging/rooms/{room_id}/messages/{message_id}/pinVerified in sessionPin or unpin a message in a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | |
| message_id | string | required |
Request body (required) — PinRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| pinned | boolean | optional | Default true. |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages/{message_id}/pin' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "pinned": true }'Response · 200
{
"id": "…",
"is_pinned": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Message not found |
| 500 | Failed to update pin |
GET/api/v1/messaging/rooms/{room_id}/messages/{root_id}/threadVerified in sessionRoot message + its replies, oldest first.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | |
| root_id | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages/{root_id}/thread' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"messages": "…",
"root_id": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Thread not found |
| 500 | Failed to load thread |
GET/api/v1/messaging/rooms/{room_id}/messages/searchVerified in sessionServer-side search over the FULL room history.
Server-side search over the FULL room history. `#tag` queries match the hashtags extracted at send time; anything else is a substring match.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| q | string | required | |
| limit | integer | optional | Default 30. max 100. |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/messages/search?q=<string>' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"query": "…",
"results": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Search failed |
GET/api/v1/messaging/rooms/{room_id}/pinsVerified in sessionPinned messages in a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/pins' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"pins": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Failed to list pins |
GET/api/v1/messaging/rooms/{room_id}/pollsVerified in sessionPoll snapshots for a room.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/polls' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"polls": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Failed to list polls |
POST/api/v1/messaging/rooms/{room_id}/pollsVerified in sessionCreate a poll in a room (question plus 2-12 options).
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request body (required) — CreatePollRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| question | string | required | |
| options | string[] | required | |
| allows_multiple | boolean | optional | Default false. |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/polls' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "question": "<string>", "options": [ "<string>" ], "allows_multiple": false }'Response · 200
The poll snapshot object.
Errors
| Status | detail |
|---|---|
| 422 | A poll needs a question and at least 2 options |
| 500 | Failed to create poll message |
| 500 | Failed to create poll |
POST/api/v1/messaging/rooms/{room_id}/readVerified in sessionRecord read receipts for every message from other senders in this room that the caller hasn't read yet, and broadcast so senders' ticks update.
Record read receipts for every message from other senders in this room that the caller hasn't read yet, and broadcast so senders' ticks update.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/read' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"marked": "…",
"room_id": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Invalid user |
| 500 | Failed to mark read |
POST/api/v1/messaging/rooms/{room_id}/scheduledVerified in sessionSchedule a message to be posted to a room later.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request body (required) — ScheduleMessageRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| content | string | required | |
| scheduled_for | string | required | |
| message_type | string | optional | Default "text". |
| recurrence | object? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/scheduled' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "content": "<string>", "scheduled_for": "<string>", "message_type": "text", "recurrence": {} }'Response · 200
{
"content": "…",
"id": "…",
"is_recurring": "…",
"room_id": "…",
"scheduled_for": "…"
}Errors
| Status | detail |
|---|---|
| 422 | Content cannot be empty |
| 422 | scheduled_for is in the past |
| 422 | recurrence.interval_minutes must be an integer >= 5 |
| 422 | scheduled_for must be an ISO timestamp |
| 500 | Failed to schedule message |
GET/api/v1/messaging/rooms/{room_id}/settingsVerified in sessionReturn the caller's per-room flags (mute/favorite/blocked/etc.).
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/settings' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"room_id": "…",
"settings": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Room not found |
| 500 | Internal server error |
PUT/api/v1/messaging/rooms/{room_id}/settingsVerified in sessionPersist the caller's per-room flags.
Persist the caller's per-room flags. is_muted → column; the rest → notification_settings.vx_flags. Partial update (only sent fields change).
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request body (required) — ParticipantSettingsRequest
| Field | Type | Required | Notes |
|---|---|---|---|
| is_muted | boolean? | optional | |
| is_favorite | boolean? | optional | |
| is_blocked | boolean? | optional | |
| is_reported | boolean? | optional | |
| is_archived | boolean? | optional | |
| disappearing_messages | string? | optional | |
| added_to_list | string? | optional | |
| cleared_at | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X PUT 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/settings' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "is_muted": false, "is_favorite": false, "is_blocked": false, "is_reported": false, "is_archived": false, "disappearing_messages": "<string>", "added_to_list": "<string>", "cleared_at": "<string>" }'Response · 200
{
"room_id": "…",
"settings": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Room not found |
| 403 | Not a participant of this room |
| 500 | Internal server error |
GET/api/v1/messaging/rooms/{room_id}/usersVerified in sessionRoom participants with their presence state.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required | Room ID |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/users' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"count": "…",
"users": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Internal server error |
POST/api/v1/messaging/rooms/{room_id}/webhookVerified in sessionReturns a tokenized URL that external systems (CI, Grafana, vxalerts) can POST {"text": "..."} to.
Returns a tokenized URL that external systems (CI, Grafana, vxalerts) can POST {"text": "..."} to. Token is stored in chat_rooms.settings.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| room_id | string | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/messaging/rooms/{room_id}/webhook' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"note": "…",
"room_id": "…",
"webhook_path": "…"
}Errors
| Status | detail |
|---|---|
| 500 | Failed to create webhook |
GET/api/v1/messaging/rooms/discoverCalled liveList joinable rooms the caller isn't in yet — for the Browse Channels / Browse Groups UI.
List joinable rooms the caller isn't in yet — for the Browse Channels / Browse Groups UI. Groups are always tenant-scoped regardless of `scope`; channels honor the scope parameter.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| search | string? | optional | Filter by room name or description |
| limit | integer | optional | Default 100. min 1. max 200. |
| scope | string | optional | 'organization' (default, same tenant) or 'all' (every public room) Default "organization". |
| room_type | string | optional | 'public' (channels, default) or 'group' (org squads — always tenant-scoped) Default "public". |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rooms/discover' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"rooms": [
{
"id": "str",
"name": "str",
"description": "str",
"room_type": "str",
"is_active": "bool",
"is_public": "bool",
"is_global": "bool",
"created_at": "str",
"updated_at": "str",
"last_activity": "str",
"created_by_id": "int",
"participant_count": "int"
},
"...x1"
],
"count": "int"
}Errors
| Status | detail |
|---|---|
| 500 | Internal server error |
GET/api/v1/messaging/rtc/iceCalled liveSTUN/TURN config + the mesh caps the server will actually enforce.
STUN/TURN config + the mesh caps the server will actually enforce. `turn_configured` is false on this deployment and `warning` says why: with no relay, symmetric-NAT peers never connect. The client is expected to show that as a real error rather than an endless 'connecting…'.
Request
curl 'https://api.vxcloud.io/api/v1/messaging/rtc/ice' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"ice_servers": [
{
"urls": [
"...",
"...x1"
]
},
"...x1"
],
"max_mesh": {
"audio": "int",
"video": "int"
},
"turn_configured": "bool",
"ring_timeout_seconds": "int",
"ring_fanout_max": "int",
"warning": "str"
}GET/api/v1/messaging/scheduledCalled liveThe caller's scheduled messages.
Request
curl 'https://api.vxcloud.io/api/v1/messaging/scheduled' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"scheduled": [
"<empty list>"
]
}DELETE/api/v1/messaging/scheduled/{scheduled_id}Verified in sessionCancel one of the caller's scheduled messages.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| scheduled_id | string | required |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/messaging/scheduled/{scheduled_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"cancelled": "…"
}GET/api/v1/messaging/usersCalled liveList active users available to start a direct message with.
List active users available to start a direct message with. ALWAYS scoped to the caller's organization. `scope=all` used to return every active user on the platform — id, name, username, email and organization_id — to anyone with a valid token, which is a cross-tenant directory dump and, with guests in the picture, one an external customer could ask for. It is now honored only for platform superusers; everyone else is silently org-scoped, which is what the UI wanted anyway. Excludes the caller. Augments each row with a live presence status from Redis when available; otherwise reports 'offline'.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| search | string? | optional | Filter by name, email, username, or @username |
| limit | integer | optional | Max users to return Default 200. min 1. max 500. |
| scope | string | optional | 'organization' (default, same tenant). 'all' is honored for platform superusers only — every other caller is org-scoped. Default "organization". |
Request
curl 'https://api.vxcloud.io/api/v1/messaging/users' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"users": [
{
"id": "str",
"name": "str",
"username": "str",
"email": "str",
"role": "str",
"avatar": "null",
"avatar_color": "str",
"organization_id": "str",
"status": "str",
"last_seen": "str"
},
"...x4"
],
"count": "int"
}Errors
| Status | detail |
|---|---|
| 500 | Internal server error |