Conversations

A conversation is either direct (two people) or group. Every endpoint here takes an end-user JWT and acts as that user. See Conversations & roles for the model.

Not available: there is no API to delete a conversation, to change a member's role after they joined, or to list conversations with an API key.

The conversation object

{
  "id": "c8a1f4e2-6b3d-4f7a-9c2e-1d5b8a0f3e61",
  "tenant_id": "65787825-3152-4c1e-9b8a-0d6f4e2a7c11",
  "type": "group",
  "name": "Engineering",
  "avatar_url": null,
  "created_by_user_id": "11111111-2222-3333-4444-555555555555",
  "member_limit": null,
  "custom_data": {},
  "settings": {},
  "last_message_at": "2026-06-02T08:13:55.123456Z",
  "last_message_preview": "ship it",
  "last_sequence": 184,
  "unread_count": 3,
  "member_external_ids": ["acme-alice", "acme-bob", "acme-charlie"],
  "inserted_at": "2026-04-12T09:00:00.000000Z",
  "updated_at": "2026-06-02T08:13:55.123456Z"
}
FieldTypeDescription
iduuid
tenant_iduuid
type"direct" | "group"
namestring | nullUp to 255 characters.
avatar_urlstring | nullUp to 2048 characters.
created_by_user_iduuid | nullThe creator. They are the conversation's owner.
member_limitinteger | nullMaximum number of members of a group, the owner included. null means no limit.
custom_dataobjectFree-form JSON.
settingsobjectFree-form JSON.
last_message_atstring | nullTime of the latest message.
last_message_previewstring | nullThe start of the latest message's text, for an inbox row. "Attachment" or "N attachments" for a message with no text.
last_sequenceintegerThe highest message sequence used so far in this conversation.
unread_countinteger | nullThe caller's unread messages: non-deleted messages after their read position. Only set by GET /v1/conversations. It is null on the other endpoints.
member_external_idsstring[]The external_id of every member, the caller included.
inserted_at, updated_atstringISO 8601.

List conversations

GET/v1/conversations
End-user JWT

The conversations the caller is a member of, most recently active first (by last_message_at, or creation time for a conversation with no messages).

Query parameters

NameTypeDefaultDescription
limitinteger501–100. Values outside the range are clamped.

There is no cursor. The endpoint returns at most 100 conversations.

Response

200: { "data": [ Conversation, … ] }, with unread_count set on each.

curl "https://api.poolse.dev/v1/conversations?limit=20" \
  -H "Authorization: Bearer $USER_JWT"

Create a conversation

POST/v1/conversations
End-user JWT

The caller becomes the owner. The other people you list join as member.

Request body

FieldTypeRequiredDescription
type"direct" | "group"yes
member_external_idsstring[]noThe other people, by your external_id. Unknown ids are created as new users with only external_id set.
namestringnoUp to 255 characters.
avatar_urlstringnoUp to 2048 characters.
member_limitintegernoGreater than 0. Groups only. Counts every member, the owner included.
custom_dataobjectnoFree-form JSON.
settingsobjectnoFree-form JSON.

Direct conversations are deduplicated. When type is direct and member_external_ids names exactly one other person, and a direct conversation between the two already exists, you get that existing conversation back instead of a new one. A direct conversation holds at most 2 members.

Response

201: the conversation, also when an existing direct conversation was returned.

curl -X POST https://api.poolse.dev/v1/conversations \
  -H "Authorization: Bearer $USER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"type":"group","name":"Launch chat","member_external_ids":["acme-alice","acme-bob"]}'

Errors

CodeStatusWhen
validation_failed422type missing or not direct / group, or a field fails validation.
member_add_failed422A direct conversation with more than one other person.
lazy_provision_rate_limited429The caller has created more than 50 unknown users in the last hour. Retry-After header in seconds, and details: { retry_after_ms }.

Get a conversation

GET/v1/conversations/:id
End-user JWT

200: the conversation (unread_count is null).

CodeStatusWhen
forbidden403The caller is not a member.
not_found404No such conversation in the tenant.

Update a conversation

PATCH/v1/conversations/:id
End-user JWT

Changes any of name, avatar_url, member_limit, custom_data and settings. Other fields are ignored. The caller must be the owner or an admin.

curl -X PATCH https://api.poolse.dev/v1/conversations/$CONV_ID \
  -H "Authorization: Bearer $USER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name":"Launch chat (archived)"}'

200: the updated conversation.

CodeStatusWhen
forbidden403The caller is not an owner or admin.
not_found404No such conversation in the tenant.
validation_failed422A field fails validation.

Members

The member object

{
  "id": "0b6e9d1c-7a2f-4c8e-b3d5-9f1a2e4c6b80",
  "tenant_id": "65787825-3152-4c1e-9b8a-0d6f4e2a7c11",
  "conversation_id": "c8a1f4e2-6b3d-4f7a-9c2e-1d5b8a0f3e61",
  "user_id": "11111111-2222-3333-4444-555555555555",
  "external_id": "acme-alice",
  "role": "owner",
  "joined_at": "2026-04-12T09:00:00.000000Z",
  "muted_until": null,
  "last_read_message_id": "5f0c2a7e-8d1b-4e3f-a6c9-2b7d0e4f1a38",
  "last_read_sequence": 181,
  "last_read_at": "2026-06-02T08:10:02.000000Z",
  "inserted_at": "2026-04-12T09:00:00.000000Z",
  "updated_at": "2026-06-02T08:10:02.000000Z"
}

role is owner, admin or member. The last_read_* fields are the member's read position.

List members

GET/v1/conversations/:conversation_id/members
End-user JWT

200: { "data": [ Member, … ] }. Any member may call it.

Other members' last_read_message_id, last_read_sequence and last_read_at are null when the workspace's read-receipt setting hides them from the caller. The caller's own are always present. See Read receipts & unread. Rows may also carry a share_read_receipts boolean.

CodeStatusWhen
forbidden403The caller is not a member.
not_found404No such conversation.

Add members

POST/v1/conversations/:conversation_id/members
End-user JWT

The caller must be the owner or an admin.

FieldTypeRequiredDescription
external_idsstring[]noThe people to add, by your external_id. Unknown ids are created as new users. Omitted or empty adds nobody and returns an empty list.
role"owner" | "admin" | "member"noRole for everyone added. Default member. Any other value is treated as member.
curl -X POST https://api.poolse.dev/v1/conversations/$CONV_ID/members \
  -H "Authorization: Bearer $USER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"external_ids":["acme-bob","acme-charlie"]}'

201: { "data": [ Member, … ] } with the memberships created. People who were already members are skipped silently and are not in the list.

CodeStatusWhen
forbidden403The caller is not an owner or admin.
not_found404No such conversation.
member_add_failed422A direct conversation already has 2 members, or the group reached its member_limit. Nobody after the failing person is added.
validation_failed422An external_id is invalid.
lazy_provision_rate_limited429The caller has created more than 50 unknown users in the last hour. Retry-After header in seconds, and details: { retry_after_ms }.

Remove a member

DELETE/v1/conversations/:conversation_id/members/:user_id
End-user JWT

user_id is the member's poolse id, not their external_id. Any member can remove themselves. An owner or admin can remove anyone, including the owner. Nothing stops the last owner from leaving.

204 on success. If the removed user has the conversation's realtime channel open, it is closed at once: they stop receiving the conversation's events.

CodeStatusWhen
forbidden403Removing someone else without being owner or admin.
not_found404No such conversation, or that user is not a member.

Realtime and webhooks

Changes here also reach clients and your backend without polling:

  • Every member of a new conversation, the creator included, gets conversation:created on their user channel once it has been saved, with all members in member_external_ids. A user added later gets it too. See User channel.
  • Your backend can receive conversation.created, conversation.updated, member.added and member.removed. See Webhooks.
Conversations · API · poolse docs