Connect your systems

Create conversations and messages

Open a conversation from your own system, send replies through the channel, change its status and assignment, and keep everything safe to retry.

Last updated

Open a conversation

POST /conversations needs an inbox_id and a contact: either contact_id or an email, which resolves an existing contact by identity or creates one. An optional content becomes the first outbound message, delivered through the inbox's channel.

curl -X POST "$KOMMIO_URL/conversations" \
  -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: renewal-2026-ana" \
  -d '{"inbox_id": 3, "email": "[email protected]", "subject": "Your renewal", "priority": "high",
       "custom_attributes": {"plan": "team", "renews_on": "2026-10-01"},
       "content": "Hi Ana, your plan renews on 1 October. Anything to change?"}'

Send a reply or a private note

kind is reply (default, delivered to the customer) or note (visible only to people whose role can read notes). content_type is text (default), html (sanitised on write) or markdown.

curl -X POST "$KOMMIO_URL/conversations/4821/messages" \
  -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"content": "Renewed on the team plan. Invoice attached to your account.", "kind": "reply"}'

curl -X POST "$KOMMIO_URL/conversations/4821/messages" \
  -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"content": "Billing confirmed the card on file works.", "kind": "note"}'
Response 201 Created
{ "id": 55011, "conversation_id": 4821, "direction": "outbound", "kind": "reply", "content_type": "text", "content": "Renewed on the team plan.", "delivery_status": "queued", "created_at": "2026-09-22T09:14:00Z" }

Delivery is asynchronous: the message is stored, then the channel sends it. Subscribe to message.delivered and message.delivery_failed to learn the outcome, or read delivery_status later.

Mention a teammate in a note

GET /conversations/{id}/mentionable?q= lists the members who may be mentioned: active members whose role sees the conversation and its private notes (the caller needs messages:write_private). Pass their ids as mention_ids on a note; mentioning someone who cannot see the note is refused with 422. A note sent without ids is resolved from its text (@First Last or @first) among the same people, and each mentioned person gets a mention notification in the app, by email or as a browser push, as they chose.

curl -X POST "$KOMMIO_URL/conversations/4821/messages" \
  -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"content": "@Lea can you check the refund?", "kind": "note", "mention_ids": [7]}'

One conversation also downloads as a transcript in text, HTML or JSON: see exports.

Change status, priority, assignment

PATCH /conversations/{id} is the single write path the UI, automations and bots all use. Send only the fields you change.

# assign to a member and a team, set priority
curl -X PATCH "$KOMMIO_URL/conversations/4821" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"assignee_id": 7, "team_id": 2, "priority": "urgent"}'

# snooze until a time
curl -X PATCH "$KOMMIO_URL/conversations/4821" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"status": "snoozed", "snoozed_until": "2026-09-23T09:00:00Z"}'

# resolve
curl -X PATCH "$KOMMIO_URL/conversations/4821" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"status": "resolved"}'

# replace tags
curl -X PUT "$KOMMIO_URL/conversations/4821/tags" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"tag_ids": [4, 9]}'

Customer messages from your own channel

An API inbox is a channel you run yourself: a form, an in-app messenger, a marketplace. Record what the customer writes with POST /inboxes/{id}/messages (inboxes:manage); it takes the normal inbound path, so it finds or creates the contact, threads into the conversation named by reply_to, else the contact's open conversation in that inbox, else starts a new one, and runs rules, bots and webhooks exactly as an email or a chat would. Agents' replies reach you as message.created webhooks with direction: "outbound".

curl -X POST "$KOMMIO_URL/inboxes/5/messages" \
  -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"from": {"kind": "external", "value": "crm:9917", "name": "Ana Souza"},
       "subject": "Problem with my order", "content": "The package arrived damaged.", "message_id": "form-88121"}'
Response 201 Created
{ "conversation_id": 4822, "created": true, "message": { "id": 55020, "direction": "inbound", "kind": "reply", "content": "The package arrived damaged.", "content_type": "text", "created_at": "2026-09-22T09:12:00Z" } }

from.kind is email, phone or external (your own user id). message_id is your id for the message: sending it again returns the stored message with created: false instead of a duplicate. reply_to names the message this answers, to thread into that conversation. Only API inboxes take messages this way; other channels receive them from their provider.

Attachments

Upload with POST /conversations/{id}/messages/upload (multipart: content, kind, one or more files). Files are checked by magic bytes and size, stored under the workspace, and served from /api/attachments/{id} as downloads unless they are images.

curl
curl -X POST "$KOMMIO_URL/conversations/4821/messages/upload" -H "Authorization: Bearer $KOMMIO_KEY" \
  -F content="Invoice attached." -F kind=reply -F [email protected]

Retry safety

Any POST, PUT, PATCH or DELETE accepts Idempotency-Key. The first response is stored for 24 hours and replayed to a retry with the same key and body, marked Idempotent-Replayed: true; a different body with the same key is rejected with 422. The TypeScript client sets a key on every write automatically.

Every claim on these pages is covered by a test in the repository, and the API reference is generated from the same contract every server serves at /api/openapi. Corrections to [email protected].