Skip to main content

Conversations

Read conversation history and messages, move threads through statuses, draft AI replies, and take a thread over from the bot.

Overview

A conversation is one thread between a customer and a bot on a channel, with an ordered list of messages. Every conversation belongs to a bot and is scoped to your tenant.

Conversations are created by the platform when a customer reaches one of your bots. There is no create or delete endpoint on this surface.

Credential class: dashboard JWT or a cm_ key with scopes

Both credentials work.

Authorization: Bearer <jwt_access_token>
# or
Authorization: Bearer cm_your_api_key
EndpointsScope needed by a cm_ key
List, search, get, messagesconversations:read
Status update, AI draft, mark read, autoreply toggleconversations:write

A dashboard JWT bypasses the scope check. No extra role rule applies here: any member of the tenant can use every endpoint on this page.

Base URL for every example: https://api.callmissed.com. Errors are {"detail": "..."}; validation failures are 422. A missing scope returns 403 naming the scope.

Enumerations

FieldValues
channelwhatsapp, voice, web
statusactive, completed, escalated, failed
role (message)user for the customer, assistant for the bot or agent
status (message)sent, delivered, read, failed. Inbound rows carry sent

GET /api/v1/conversations

Lists conversations. Requires conversations:read.

By default the newest conversation comes first (sort=created) and you page with offset. Pass sort=last_activity to get the inbox order instead: the conversation with the newest message first. That order changes every time a message arrives, so it pages with a cursor, not an offset: send each row's cursor value from the last row of a page as cursor= to get the next page.

ParameterTypeRequiredConstraints
channelstringNowhatsapp, voice, or web. An unknown value returns 422 with a generic invalid-input message
statusstringNoactive, completed, escalated, or failed. Same behaviour on an unknown value
bot_idUUIDNoRestrict to one agent
limitintegerNo1 <= limit <= 500, default 200
offsetintegerNo0 <= offset <= 100000, default 0. Only with sort=created: a non-zero offset with sort=last_activity returns 400
sortstringNocreated (default) or last_activity
cursorstringNoThe cursor of the last row of the previous page. Only with sort=last_activity, otherwise 400. A malformed cursor returns 400
unreadbooleanNotrue: only threads with unread customer messages; false: only the rest
assignedstringNounassigned, or a teammate's user id
labelstringNoThreads carrying this label. Case-insensitive; surrounding spaces and invisible characters are ignored, as they are when labels are saved
# Inbox order, first page, then the next one
curl "https://api.callmissed.com/api/v1/conversations?sort=last_activity&limit=50" \
  -H "Authorization: Bearer cm_your_api_key"
curl "https://api.callmissed.com/api/v1/conversations?sort=last_activity&limit=50&cursor=WyIyMDI2LTA4LTA0VDEyOjAxOjAwKzAwOjAwIiwiYzBmZmVlMDAiXQ" \
  -H "Authorization: Bearer cm_your_api_key"
curl "https://api.callmissed.com/api/v1/conversations?channel=whatsapp&status=active&limit=200" \
  -H "Authorization: Bearer cm_your_api_key"
[
  {
    "id": "c0ffee00-1111-2222-3333-444455556666",
    "tenant_id": "a0b1c2d3-4455-6677-8899-aabbccddeeff",
    "bot_id": "b1f2c3d4-5678-90ab-cdef-1234567890ab",
    "channel": "whatsapp",
    "external_id": "+919876543210",
    "status": "active",
    "duration_seconds": null,
    "metadata": null,
    "ai_autoreply_enabled": true,
    "created_at": "2026-08-04T11:55:00Z",
    "updated_at": "2026-08-04T12:01:00Z",
    "bot_name": "Support Bot",
    "last_message": "Where is my order?",
    "last_message_at": "2026-08-04T12:01:00Z",
    "unread_count": 2,
    "last_read_at": "2026-08-04T11:58:00Z",
    "last_message_preview": "Where is my order?",
    "last_message_type": "text",
    "last_message_direction": "in",
    "last_message_status": null,
    "display_name": "Asha Rao",
    "contact_name": "Asha Rao",
    "profile_name": "Asha",
    "cursor": null
  }
]
FieldTypeNotes
external_idstringThe customer's channel identity, for example the WhatsApp phone number
duration_secondsinteger | nullPopulated for voice threads
metadataobject | nullFree-form JSON attached by the platform
ai_autoreply_enabledbooleanWhether the bot still answers automatically on this thread
bot_namestring"" when the bot has been deleted
last_messagestring | nullThe most recent message as stored
last_message_atstring | nullWhen the most recent message was sent. null when the thread has none
unread_countintegeruser messages created after last_read_at
last_read_atstring | nullnull means the thread was never opened
last_message_previewstring | nullWhat an inbox shows for the most recent message: its text, or for media and interactive messages the caption, file name or a label such as Photo or Voice message (0:07)
last_message_typestring | nulltext, image, audio, video, document, sticker, location, contacts, interactive, button, reaction, template or system
last_message_directionstring | nullin from the customer, out from your business
last_message_statusstring | nullOutbound only: sent, delivered, read or failed
display_namestring | nullThe CRM contact's name, else the customer's WhatsApp profile name
contact_namestring | nullThe linked CRM contact's name
profile_namestring | nullThe name the customer set on WhatsApp
cursorstring | nullWith sort=last_activity: pass the last row's value as cursor= for the next page. null otherwise

GET /api/v1/conversations/search

Full-text search over your conversations' messages, newest first. Requires conversations:read.

It searches what an inbox shows: a text message's words, or a media or interactive message's caption, file name, contact names or button titles. Every word you send must appear; each matches as a prefix, so ord finds order. Words match as typed, with no stemming, so Hindi, Hinglish and order numbers work.

ParameterTypeRequiredConstraints
qstringYes1 to 200 characters
limitintegerNo1 <= limit <= 50, default 20
cursorstringNonext_cursor from the previous page
conversation_idUUIDNoSearch within one conversation
curl "https://api.callmissed.com/api/v1/conversations/search?q=refund&limit=20" \
  -H "Authorization: Bearer cm_your_api_key"
{
  "results": [
    {
      "conversation_id": "c0ffee00-1111-2222-3333-444455556666",
      "message_id": "m1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8",
      "created_at": "2026-08-04T11:55:00Z",
      "direction": "in",
      "message_type": "text",
      "snippet": "Can I get a refund for order 5512?",
      "highlights": [{ "start": 12, "end": 18 }],
      "channel": "whatsapp",
      "external_id": "+919876543210",
      "display_name": "Asha Rao",
      "cursor": "WyIyMDI2LTA4LTA0VDExOjU1OjAwKzAwOjAwIiwibTFhMmIzYzQiXQ"
    }
  ],
  "next_cursor": null
}

highlights are offsets into snippet in UTF-16 code units, the same as JavaScript string indices. next_cursor is null on the last page.

StatusCause
400Invalid cursor, or Search is too broad. Add more letters or another word.
403Key missing conversations:read
422q empty or over 200 characters, or limit out of range

GET /api/v1/conversations/{conversation_id}

One conversation, same shape as a list row. Requires conversations:read.

curl https://api.callmissed.com/api/v1/conversations/c0ffee00-1111-2222-3333-444455556666 \
  -H "Authorization: Bearer cm_your_api_key"

404 Conversation not found when the id is not in your tenant; 422 when it is not a valid UUID.

GET /api/v1/conversations/{conversation_id}/messages

The full thread in chronological order. No pagination parameters: the whole thread is returned. Requires conversations:read.

curl https://api.callmissed.com/api/v1/conversations/c0ffee00-1111-2222-3333-444455556666/messages \
  -H "Authorization: Bearer cm_your_api_key"
[
  {
    "id": "m1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8",
    "conversation_id": "c0ffee00-1111-2222-3333-444455556666",
    "role": "user",
    "content": "Where is my order?",
    "message_type": "text",
    "tokens_used": null,
    "status": "sent",
    "created_at": "2026-08-04T11:55:00Z"
  },
  {
    "id": "m2b3c4d5-e6f7-4081-92a3-b4c5d6e7f809",
    "conversation_id": "c0ffee00-1111-2222-3333-444455556666",
    "role": "assistant",
    "content": "Let me check that for you.",
    "message_type": "text",
    "tokens_used": 18,
    "status": "delivered",
    "created_at": "2026-08-04T11:55:02Z"
  }
]

404 Conversation not found; 403 without conversations:read.

PUT /api/v1/conversations/{conversation_id}/status

Moves a thread between statuses. Requires conversations:write.

FieldTypeRequiredConstraints
statusstringYesExactly one of active, completed, escalated, failed
curl -X PUT https://api.callmissed.com/api/v1/conversations/c0ffee00-1111-2222-3333-444455556666/status \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'

Returns the updated conversation, same shape as a list row.

403 without conversations:write; 404 when not found; 422 on an invalid status value.

POST /api/v1/conversations/{conversation_id}/read

Marks the thread read as of now by bumping last_read_at. Idempotent, safe to call on every inbox open. No required body. Requires conversations:write.

curl -X POST https://api.callmissed.com/api/v1/conversations/c0ffee00-1111-2222-3333-444455556666/read \
  -H "Authorization: Bearer cm_your_api_key"

Returns the conversation, same shape as a list row, with unread_count recomputed after the bump (normally 0).

403 without conversations:write; 404 when not found.

POST /api/v1/conversations/{conversation_id}/autoreply

Per-thread gate on the bot's automatic replies. Set enabled: false when a human takes the thread over; the inbound handler then skips the bot reply until it is flipped back. Requires conversations:write.

FieldTypeRequiredConstraints
enabledbooleanYes-
curl -X POST https://api.callmissed.com/api/v1/conversations/c0ffee00-1111-2222-3333-444455556666/autoreply \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'

Returns the conversation, same shape as a list row, with the new ai_autoreply_enabled. 403 without conversations:write; 404 when not found.

POST /api/v1/conversations/{conversation_id}/ai-draft

Generates a suggested reply for a human to review. It never sends anything. Pair it with autoreply: false to use the model as a co-pilot on a thread a human has taken over. Requires conversations:write.

FieldTypeRequiredConstraints
instructionstring | nullNoMax 1000 chars. Extra steering for this draft only, for example shorter or apologise for the delay
curl -X POST https://api.callmissed.com/api/v1/conversations/c0ffee00-1111-2222-3333-444455556666/ai-draft \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"instruction": "apologise for the delay and offer to check the tracking"}'
{
  "draft": "Sorry about the wait. Let me pull up your tracking details right now and get back to you in a minute.",
  "used_default_prompt": false
}

used_default_prompt is true when the linked bot has no system prompt, in which case a built-in support-agent persona is used. The draft is clamped to 4096 characters so it is always sendable on WhatsApp.

This call is billed at the same LLM token rates as the auto-reply path.

StatusCause
400No messages in this conversation yet — nothing to draft from.
403Key missing conversations:write
404Conversation not found
502AI draft failed; please retry. or AI returned an empty draft — please retry.