# Events

The event feed: the first message to a lead and the lead's reply. Read it with a cursor to learn about changes without scanning every lead.

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

Endpoints:

- `GET /v1/events`: List events

## The lead.first_message_sent event

A first message was sent to the lead. `data.first_message` is present only with `include_text=true`.

| Field | Type | Description |
|---|---|---|
| `object` | string | Object type, always `event`. |
| `id` | string | Event ID: `evt_` followed by 32 hexadecimal characters. The deduplication key. |
| `api_version` | string | Event format version. Always `"2026-10-18"`. |
| `timestamp` | string (date-time) | When the fact occurred. |
| `test` | boolean | Always `false`. |
| `type` | string | Event type. Always `"lead.first_message_sent"`. |
| `data` | object | Event data. |
| `data.organization_id` | string (uuid) | Organization where the event occurred. |
| `data.workspace` | object | Workspace at the time of the event. |
| `data.workspace.id` | string (uuid) | ID. |
| `data.workspace.name` | string or null | Name. Can be `null`. |
| `data.campaign` | object or null | Campaign at the time of the event. Can be `null`. |
| `data.campaign.id` | string (uuid) | ID. |
| `data.campaign.name` | string or null | Name. Can be `null`. |
| `data.lead` | object | Lead snapshot. |
| `data.lead.id` | string (uuid) | Lead ID. |
| `data.lead.username` | string or null | Telegram username without `@`. Can be `null`. |
| `data.lead.status` | string | Lead status at the time of the event. Values: `new`, `contacted`, `completed`, `replied`, `blocked`, `failed`, `suspended`, `deleted`, `opted_out`, `unknown`. |
| `data.lead.fields` | object | Contact record fields at the time of the event. |
| `data.lead.fields.record_id` | string or null | Record ID in an external CRM, passed when the contacts were uploaded. `null` if `record_id_status` is not `valid`. May be absent. |
| `data.lead.fields.first_name` | string or null | First name. May be absent. |
| `data.lead.fields.last_name` | string or null | Last name. May be absent. |
| `data.lead.fields.company` | string or null | Company. May be absent. |
| `data.lead.fields.position` | string or null | Job title. May be absent. |
| `data.lead.fields.website` | string or null | Website. May be absent. |
| `data.lead.fields.source` | string or null | Contact source. May be absent. |
| `data.lead.fields.utm_source` | string or null | The `utm_source` tag. May be absent. |
| `data.lead.fields.utm_medium` | string or null | The `utm_medium` tag. May be absent. |
| `data.lead.fields.utm_campaign` | string or null | The `utm_campaign` tag. May be absent. |
| `data.lead.fields_truncated` | boolean | `true` if some values were shortened. |
| `data.lead.record_id_status` | string | Reliability of `fields.record_id`. In v1 events return `unknown`, and `fields.record_id` is `null`: get `record_id` from `GET /v1/leads/{id}` using `data.lead.id`. Values: `valid`, `missing`, `conflict`, `unknown`. |
| `data.account` | object or null | Account that handled the conversation. `null` if unknown. |
| `data.account.id` | string (uuid) | Account ID. |
| `data.account.username` | string or null | Account username without `@`. Can be `null`. |
| `data.account.name` | string or null | Account display name. Can be `null`. |
| `data.first_message_sent_at` | string (date-time) or null | When the first message was sent. `null` if sending is not confirmed; this is common for reply events. |
| `data.first_message` | object | First message text. Only with `include_text=true`. May be absent. |
| `data.first_message.text` | string or null | Saved text, up to 4000 characters. `null` if the text is unavailable. |
| `data.first_message.text_truncated` | boolean or null | `true`: the text is shortened; `false`: the text is complete; `null`: completeness is unknown. |
| `data.first_message.text_unavailable` | boolean | `true` if the text is not returned. |
| `data.first_message.text_unavailable_reason` | string or null | Reason the text is unavailable, or `null`. Values: `not_saved`, `ownership_unproven`, `source_conflict`, `purged`. |

Example:

```json
{
  "object": "event",
  "id": "evt_3b9e2c7a1f4d4e8a9b6c0d2e5f7a8c91",
  "type": "lead.first_message_sent",
  "api_version": "2026-10-18",
  "timestamp": "2026-10-26T07:30:00.000Z",
  "test": false,
  "data": {
    "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
    "workspace": {
      "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
      "name": "Main workspace"
    },
    "campaign": {
      "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
      "name": "Example campaign"
    },
    "lead": {
      "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
      "username": "example_user",
      "status": "contacted",
      "fields": {
        "record_id": null,
        "first_name": "Anna",
        "company": "Example Ltd"
      },
      "fields_truncated": false,
      "record_id_status": "unknown"
    },
    "account": {
      "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
      "username": "example_sender",
      "name": "Mary from Example"
    },
    "first_message_sent_at": "2026-10-26T07:30:00.000Z"
  }
}
```

## The lead.replied event

The lead replied. Sent for every counted reply, so one lead can have several. Text fields in `data.reply` are present only with `include_text=true`.

| Field | Type | Description |
|---|---|---|
| `object` | string | Object type, always `event`. |
| `id` | string | Event ID: `evt_` followed by 32 hexadecimal characters. The deduplication key. |
| `api_version` | string | Event format version. Always `"2026-10-18"`. |
| `timestamp` | string (date-time) | When the fact occurred. |
| `test` | boolean | Always `false`. |
| `type` | string | Event type. Always `"lead.replied"`. |
| `data` | object | Event data. |
| `data.organization_id` | string (uuid) | Organization where the event occurred. |
| `data.workspace` | object | Workspace at the time of the event. |
| `data.workspace.id` | string (uuid) | ID. |
| `data.workspace.name` | string or null | Name. Can be `null`. |
| `data.campaign` | object or null | Campaign at the time of the event. Can be `null`. |
| `data.campaign.id` | string (uuid) | ID. |
| `data.campaign.name` | string or null | Name. Can be `null`. |
| `data.lead` | object | Lead snapshot. |
| `data.lead.id` | string (uuid) | Lead ID. |
| `data.lead.username` | string or null | Telegram username without `@`. Can be `null`. |
| `data.lead.status` | string | Lead status at the time of the event. Values: `new`, `contacted`, `completed`, `replied`, `blocked`, `failed`, `suspended`, `deleted`, `opted_out`, `unknown`. |
| `data.lead.fields` | object | Contact record fields at the time of the event. |
| `data.lead.fields.record_id` | string or null | Record ID in an external CRM, passed when the contacts were uploaded. `null` if `record_id_status` is not `valid`. May be absent. |
| `data.lead.fields.first_name` | string or null | First name. May be absent. |
| `data.lead.fields.last_name` | string or null | Last name. May be absent. |
| `data.lead.fields.company` | string or null | Company. May be absent. |
| `data.lead.fields.position` | string or null | Job title. May be absent. |
| `data.lead.fields.website` | string or null | Website. May be absent. |
| `data.lead.fields.source` | string or null | Contact source. May be absent. |
| `data.lead.fields.utm_source` | string or null | The `utm_source` tag. May be absent. |
| `data.lead.fields.utm_medium` | string or null | The `utm_medium` tag. May be absent. |
| `data.lead.fields.utm_campaign` | string or null | The `utm_campaign` tag. May be absent. |
| `data.lead.fields_truncated` | boolean | `true` if some values were shortened. |
| `data.lead.record_id_status` | string | Reliability of `fields.record_id`. In v1 events return `unknown`, and `fields.record_id` is `null`: get `record_id` from `GET /v1/leads/{id}` using `data.lead.id`. Values: `valid`, `missing`, `conflict`, `unknown`. |
| `data.account` | object or null | Account that handled the conversation. `null` if unknown. |
| `data.account.id` | string (uuid) | Account ID. |
| `data.account.username` | string or null | Account username without `@`. Can be `null`. |
| `data.account.name` | string or null | Account display name. Can be `null`. |
| `data.first_message_sent_at` | string (date-time) or null | When the first message was sent. `null` if sending is not confirmed; this is common for reply events. |
| `data.reply` | object | Lead reply. |
| `data.reply.id` | string (uuid) | Reply ID. |
| `data.reply.replied_at` | string (date-time) | When the reply arrived. |
| `data.reply.automation_stopped` | boolean or null | Whether the sequence stopped after the reply. `null` if the stop is not confirmed. Currently always `null`. |
| `data.reply.is_first_reply` | boolean | Whether this is the lead's first reply in the campaign. Not sent at the moment. May be absent. |
| `data.reply.text` | string or null | Reply text. Only with `include_text=true`. May be absent. |
| `data.reply.text_truncated` | boolean or null | Whether the text is shortened. Only with `include_text=true`. May be absent. |
| `data.reply.text_unavailable` | boolean | Whether the text is unavailable. Only with `include_text=true`. May be absent. |
| `data.reply.text_unavailable_reason` | string or null | Reason the text is unavailable. Only with `include_text=true`. Values: `not_saved`, `ownership_unproven`, `source_conflict`, `purged`. May be absent. |

Example:

```json
{
  "object": "event",
  "id": "evt_8d4f1a6c2e9b4c7d8a3e5f0b1c6d9e27",
  "type": "lead.replied",
  "api_version": "2026-10-18",
  "timestamp": "2026-10-26T09:12:00.000Z",
  "test": false,
  "data": {
    "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
    "workspace": {
      "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
      "name": "Main workspace"
    },
    "campaign": {
      "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
      "name": "Example campaign"
    },
    "lead": {
      "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
      "username": "example_user",
      "status": "replied",
      "fields": {
        "record_id": null,
        "first_name": "Anna",
        "company": "Example Ltd"
      },
      "fields_truncated": false,
      "record_id_status": "unknown"
    },
    "account": {
      "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
      "username": "example_sender",
      "name": "Mary from Example"
    },
    "first_message_sent_at": null,
    "reply": {
      "id": "e4b7a2d9-6c1f-4e38-9a5b-0d8c3f7e1a62",
      "replied_at": "2026-10-26T09:12:00.000Z",
      "automation_stopped": null
    }
  }
}
```

## List events

`GET /v1/events`

Events in the order they occur: the first message to a lead and the lead's reply. Poll the feed with the cursor. `next_cursor` is always present, even on an empty page. With `include_text=true` the request weight is 3.

Permission: `events:read`. Request weight: 2 (3 with `include_text=true`).

### Parameters

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `limit` | query | integer | no | Number of objects to return per page. From 1 to 200. Defaults to `50`. |
| `cursor` | query | string | no | The `next_cursor` value from the previous response. Other filters must match the first request. |
| `workspace_id` | query | string (uuid) | no | Only objects in this workspace. |
| `campaign_id` | query | string (uuid) | no | Only objects in this campaign. |
| `type` | query | string | no | Only events of this type. Values: `lead.first_message_sent`, `lead.replied`. |
| `include_text` | query | string | no | Include message texts in events. Requires the `messages:read` permission and "Message text" enabled on the key. Values: `true`, `false`. Defaults to `false`. |
| `start` | query | string | no | `now`: start the feed from the current moment. Returns an empty page and a cursor. Cannot be combined with `cursor`. Values: `now`. |

### 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.available_from` | string (date-time) or null | Earliest time for which the feed has events. `null` if unknown. |
| `meta.retention_days` | string | Number of days the feed retains events. Always `30`. |
| `meta.collection` | object | Status of saved conversation collection for the API. |
| `meta.collection.state` | string | `running`: collection is active; `paused`: paused in Meliora; `preparing`: preparing after being enabled; `degraded`: data may be incomplete. Values: `running`, `paused`, `preparing`, `degraded`. |
| `meta.collection.enabled_at` | string (date-time) or null | When collection was enabled. `null` if it has not been enabled yet. |
| `meta.collection.pause_pending` | boolean | A pause or resume was requested in Meliora but has not been applied yet. |
| `meta.coverage` | object | Event feed completeness. |
| `meta.coverage.status` | string | `complete`, `partial` or `unknown`. Values: `complete`, `partial`, `unknown`. |
| `meta.coverage.gaps` | array of object | Up to 50 intervals of possible incompleteness. |
| `meta.coverage.gaps[].id` | string (uuid) | Interval ID. |
| `meta.coverage.gaps[].from` | string (date-time) | Start of the interval. |
| `meta.coverage.gaps[].to` | string (date-time) or null | End of the interval. `null` if the interval is not closed yet. |
| `meta.coverage.gaps[].state` | string | `open`, `recovering`, `recovered`, `unrecoverable` or `unknown`. Values: `open`, `recovering`, `recovered`, `unrecoverable`, `unknown`. |
| `meta.coverage.gaps_truncated` | boolean | `true` if there are more than 50 intervals. |
| `meta.coverage.incomplete_from` | string (date-time) or null | Earliest boundary of the hidden incomplete intervals. `null` if there are none. |
| `meta.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 -G https://api.melioralab.io/v1/events \
  -H "Authorization: Bearer $MELIORA_API_KEY" \
  -d limit=100
```

### Example response: Response

```json
{
  "object": "list",
  "data": [
    {
      "object": "event",
      "id": "evt_3b9e2c7a1f4d4e8a9b6c0d2e5f7a8c91",
      "type": "lead.first_message_sent",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T07:30:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Main workspace"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Example campaign"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "contacted",
          "fields": {
            "record_id": null,
            "first_name": "Anna",
            "company": "Example Ltd"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Mary from Example"
        },
        "first_message_sent_at": "2026-10-26T07:30:00.000Z"
      }
    },
    {
      "object": "event",
      "id": "evt_8d4f1a6c2e9b4c7d8a3e5f0b1c6d9e27",
      "type": "lead.replied",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T09:12:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Main workspace"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Example campaign"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "replied",
          "fields": {
            "record_id": null,
            "first_name": "Anna",
            "company": "Example Ltd"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Mary from Example"
        },
        "first_message_sent_at": null,
        "reply": {
          "id": "e4b7a2d9-6c1f-4e38-9a5b-0d8c3f7e1a62",
          "replied_at": "2026-10-26T09:12:00.000Z",
          "automation_stopped": null
        }
      }
    }
  ],
  "has_more": false,
  "next_cursor": "v1.k1.Qm9hcmRfMWZ4.c3RyaWN0bHlfb3BhcXVl.dGFnX2V4YW1wbGU.bWFjX2V4YW1wbGVfdmFsdWU",
  "meta": {
    "available_from": "2026-10-20T08:00:00.000Z",
    "retention_days": 30,
    "collection": {
      "state": "running",
      "enabled_at": "2026-10-20T08:00:00.000Z",
      "pause_pending": false
    },
    "coverage": {
      "status": "unknown",
      "gaps": [],
      "gaps_truncated": false,
      "incomplete_from": null
    },
    "observed_at": "2026-10-27T09:15:42.000Z"
  }
}
```

### Example response: With include_text=true

```json
{
  "object": "list",
  "data": [
    {
      "object": "event",
      "id": "evt_3b9e2c7a1f4d4e8a9b6c0d2e5f7a8c91",
      "type": "lead.first_message_sent",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T07:30:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Main workspace"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Example campaign"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "contacted",
          "fields": {
            "record_id": null,
            "first_name": "Anna",
            "company": "Example Ltd"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Mary from Example"
        },
        "first_message_sent_at": "2026-10-26T07:30:00.000Z",
        "first_message": {
          "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": "event",
      "id": "evt_8d4f1a6c2e9b4c7d8a3e5f0b1c6d9e27",
      "type": "lead.replied",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T09:12:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Main workspace"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Example campaign"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "replied",
          "fields": {
            "record_id": null,
            "first_name": "Anna",
            "company": "Example Ltd"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Mary from Example"
        },
        "first_message_sent_at": null,
        "reply": {
          "id": "e4b7a2d9-6c1f-4e38-9a5b-0d8c3f7e1a62",
          "replied_at": "2026-10-26T09:12:00.000Z",
          "automation_stopped": null,
          "text": "Hello! That's me, let's have a call tomorrow.",
          "text_truncated": null,
          "text_unavailable": false,
          "text_unavailable_reason": null
        }
      }
    }
  ],
  "has_more": false,
  "next_cursor": "v1.k1.Qm9hcmRfMWZ4.c3RyaWN0bHlfb3BhcXVl.dGFnX2V4YW1wbGU.bWFjX2V4YW1wbGVfdmFsdWU",
  "meta": {
    "available_from": "2026-10-20T08:00:00.000Z",
    "retention_days": 30,
    "collection": {
      "state": "running",
      "enabled_at": "2026-10-20T08:00:00.000Z",
      "pause_pending": false
    },
    "coverage": {
      "status": "unknown",
      "gaps": [],
      "gaps_truncated": false,
      "incomplete_from": null
    },
    "observed_at": "2026-10-27T09:15:42.000Z"
  }
}
```

### Example response: With start=now

```json
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": "v1.k1.Qm9hcmRfMWZ4.c3RyaWN0bHlfb3BhcXVl.dGFnX2V4YW1wbGU.bWFjX2V4YW1wbGVfdmFsdWU",
  "meta": {
    "available_from": "2026-10-20T08:00:00.000Z",
    "retention_days": 30,
    "collection": {
      "state": "running",
      "enabled_at": "2026-10-20T08:00:00.000Z",
      "pause_pending": false
    },
    "coverage": {
      "status": "unknown",
      "gaps": [],
      "gaps_truncated": false,
      "incomplete_from": null
    },
    "observed_at": "2026-10-27T09:15:42.000Z"
  }
}
```
