# Conversations

Saved messages of an account's conversation with a lead: outbound sequence steps, manual messages and replies.

Base URL: https://api.melioralab.io/v1. Pass the key in the `Authorization: Bearer mel_live_...` header.

Endpoints:

- `GET /v1/leads/{id}/messages`: List lead conversation

## The Message object

A message from the saved conversation with a lead. The archive may be incomplete: see `meta.completeness`.

| Field | Type | Description |
|---|---|---|
| `object` | string | Object type, always `message`. |
| `id` | string | Opaque, stable message ID. Do not parse its format. |
| `direction` | string | `outbound`: from the account to the lead; `inbound`: from the lead. Values: `outbound`, `inbound`. |
| `author` | string | `campaign`: a sequence step; `manager`: a manual message; `lead`: a lead reply; `unknown`: not determined. Values: `campaign`, `manager`, `lead`, `unknown`. |
| `sent_at` | string (date-time) or null | When the message was sent or received. `null` if unknown. |
| `state` | string | `sent`: sending confirmed; `received`: incoming message saved; `unknown`: outcome not confirmed. Values: `sent`, `received`, `unknown`. |
| `media_kind` | string | Attachment type. Files and links to them are not returned. Values: `none`, `photo`, `video`, `voice`, `audio`, `document`, `sticker`, `animation`, `video_note`, `contact`, `location`, `poll`, `other`, `unknown`. |
| `text` | string or null | Saved text, up to 4000 characters. `null` if the text is unavailable. |
| `text_truncated` | boolean or null | `true`: the text is shortened; `false`: the text is complete; `null`: completeness is unknown. |
| `text_unavailable` | boolean | `true` if the text is not returned. The reason is in `text_unavailable_reason`. |
| `text_unavailable_reason` | string or null | `not_saved`: the text was not saved; `ownership_unproven`: it is not proven that the message belongs to this conversation; `source_conflict`: sources disagree; `purged`: the text was deleted after the retention period. Values: `not_saved`, `ownership_unproven`, `source_conflict`, `purged`. |

Example:

```json
{
  "object": "message",
  "id": "m_6b2e9f403a7c4d158e921c5a7f3b9d08",
  "direction": "outbound",
  "author": "campaign",
  "sent_at": "2026-10-26T07:30:00.000Z",
  "state": "sent",
  "media_kind": "none",
  "text": "Good afternoon, Anna! I'm writing from Example: could you tell me who handles outreach at your company?",
  "text_truncated": false,
  "text_unavailable": false,
  "text_unavailable_reason": null
}
```

## List lead conversation

`GET /v1/leads/{id}/messages`

Saved messages of the conversation with a lead, oldest first. Requires the `messages:read` permission and "Message text" enabled on the key. The archive may be incomplete: see `meta.completeness`.

Permission: `messages:read` and “Message text” enabled on the key. Request weight: 3.

### Parameters

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | string (uuid) | yes | Lead ID. |
| `limit` | query | integer | no | Number of objects to return per page. From 1 to 100. Defaults to `50`. |
| `cursor` | query | string | no | The `next_cursor` value from the previous response. Other filters must match the first request. |

### Response

A list: `object`, `data`, `has_more`, `next_cursor`, `meta`. Pagination: https://docs.melioralab.io/en/md/pagination.md

The `meta` field:

| Field | Type | Description |
|---|---|---|
| `meta.completeness` | object | How complete the saved conversation archive with the lead is. Completeness is not calculated yet: `overall` and `inbound` are returned as `partial`, `outbound` as `unknown`. |
| `meta.completeness.overall` | string | Overall completeness: `complete`, `partial` or `unknown`. Values: `complete`, `partial`, `unknown`. |
| `meta.completeness.inbound` | string | Completeness of inbound messages. Values: `complete`, `partial`, `unknown`. |
| `meta.completeness.outbound` | string | Completeness of outbound messages. Values: `complete`, `partial`, `unknown`. |
| `meta.completeness.available_from` | string (date-time) or null | Start of the period covered by the data. `null` if unknown. |
| `meta.completeness.available_to` | string (date-time) or null | End of the period covered by the data. `null` if unknown. |
| `meta.completeness.observed_at` | string (date-time) | Time the server read the data. |

### Endpoint-specific errors

- `cursor_mismatch` (400): The cursor is corrupted, was issued for a different route or filters, or the key's permissions and scope have changed.
- `scope_required` (403): The key does not have permission for this read. Texts also require "Message text" to be enabled.
- `not_found` (404): The object was not found or is outside the key's scope. An unknown path also returns 404.
- `cursor_expired` (410): The cursor expired: 15 minutes for lists, 30 days for the event feed.

Common errors: https://docs.melioralab.io/en/md/errors.md

### Example request

```bash
curl https://api.melioralab.io/v1/leads/5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31/messages \
  -H "Authorization: Bearer $MELIORA_API_KEY"
```

### Example response

```json
{
  "object": "list",
  "data": [
    {
      "object": "message",
      "id": "m_6b2e9f403a7c4d158e921c5a7f3b9d08",
      "direction": "outbound",
      "author": "campaign",
      "sent_at": "2026-10-26T07:30:00.000Z",
      "state": "sent",
      "media_kind": "none",
      "text": "Good afternoon, Anna! I'm writing from Example: could you tell me who handles outreach at your company?",
      "text_truncated": false,
      "text_unavailable": false,
      "text_unavailable_reason": null
    },
    {
      "object": "message",
      "id": "m_c7a1d3e59b2f4a68b4d05e8f2a6c1b97",
      "direction": "inbound",
      "author": "lead",
      "sent_at": "2026-10-26T09:12:00.000Z",
      "state": "received",
      "media_kind": "none",
      "text": "Hello! That's me, let's have a call tomorrow.",
      "text_truncated": null,
      "text_unavailable": false,
      "text_unavailable_reason": null
    },
    {
      "object": "message",
      "id": "m_a3f8c6e27d1b4e949c058b2d4f7a6e13",
      "direction": "inbound",
      "author": "lead",
      "sent_at": "2026-10-26T09:13:30.000Z",
      "state": "received",
      "media_kind": "photo",
      "text": null,
      "text_truncated": null,
      "text_unavailable": true,
      "text_unavailable_reason": "not_saved"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "meta": {
    "completeness": {
      "overall": "partial",
      "inbound": "partial",
      "outbound": "unknown",
      "available_from": "2026-10-26T07:30:00.000Z",
      "available_to": "2026-10-27T09:15:42.000Z",
      "observed_at": "2026-10-27T09:15:42.000Z"
    }
  }
}
```
