# List campaign messages

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

Requires an API key with the `campaigns:read` scope. [About keys and scopes](/docs/api/authentication)

Delivery records for one campaign — one per contact per channel — including status and any provider failure reason. Filter with `channel` to see one channel. Results are cursor-paginated: pass `meta.pagination.nextCursor` back as `cursor` until it comes back `null`. The cursor is opaque — do not construct or parse one.

## Path parameters

- `id` (string, required, uuid)

## Query parameters

- `limit` (integer, optional, min > 0, max 100, default `20`)
- `cursor` (string, optional, 1–2048 characters)
- `channel` (string, optional, one of `email`, `push`) — Only this channel; a multi-channel campaign lists one row per contact per channel.
- `status` (string, optional, one of `queued`, `sending`, `skipped`, `sent`, `failed`, `delivered`, `bounced`, `complained`) — Only messages in this status.
- `email` (string, optional, 1–320 characters) — Part of the recipient's address, case-insensitive.
- `opened` (string, optional, one of `true`, `false`) — `true`: opened (or clicked) at least once; `false`: never.
- `clicked` (string, optional, one of `true`, `false`) — `true`: a link clicked at least once; `false`: never.

## Returns

Returns `200 OK` with a list of `CampaignDeliveryMessage` objects in `data`, and the next page's cursor in `meta` (see [pagination](/docs/api/pagination)). Otherwise returns an error; [Errors](/docs/api/errors) lists every code and which to retry.

- `id` (string, required, uuid)
- `contactId` (string | null, required, uuid) — Null when the contact was erased, or the message passed the retention period: the message no longer names anyone.
- `recipientAddress` (string | null, required, email) — The email address it went to; `null` for a push message.
- `channel` (string, required, one of `email`, `push`)
- `status` (string, required, one of `queued`, `sending`, `skipped`, `sent`, `failed`, `delivered`, `bounced`, `complained`) — Where the message is in delivery.
- `failureReason` (string | null, required) — Why it failed or was skipped, if it did.
- `sentAt` (string | null, required, date-time) — When it was handed to the provider.
- `deliveredAt` (string | null, required, date-time) — When delivery was confirmed.
- `failedAt` (string | null, required, date-time) — When it failed.
- `openedAt` (string | null, required, date-time) — When it was first opened.
- `clickedAt` (string | null, required, date-time) — When a link was first clicked.
- `createdAt` (string, required, date-time)

## Example

```bash
curl https://api.kirora.io/v1/campaigns/018f2a4e-6b1c-7d3e-9f40-37386ae05a6b/messages \
  -H "Authorization: Bearer $KIRORA_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "018f2a4e-6b1c-7d3e-9f40-37386ae05a6b",
      "contactId": "018f2a4e-6b1c-7d3e-9f40-4db757ce5a6b",
      "recipientAddress": "amaka@juniperkitchen.com",
      "channel": "email",
      "status": "queued",
      "failureReason": "failureReason_example",
      "sentAt": "2030-03-14T09:30:00Z",
      "deliveredAt": "2030-03-14T09:30:00Z",
      "failedAt": "2030-03-14T09:30:00Z",
      "openedAt": "2030-03-14T09:30:00Z",
      "clickedAt": "2030-03-14T09:30:00Z",
      "createdAt": "2030-03-14T09:30:00Z"
    }
  ],
  "meta": {
    "pagination": {
      "limit": 1,
      "nextCursor": "nextCursor_example"
    }
  }
}
```
