---
title: "List alert delivery records across every check in the project"
method: GET
path: "/api/v1/deliveries"
tags: ["Events"]
---

# List alert delivery records across every check in the project

`GET /api/v1/deliveries`

## Request

- Base URL: `https://app.lastping.dev`
- URL: `https://app.lastping.dev/api/v1/deliveries`
- Auth: API key in header `X-Api-Key`

Returns delivery records project-wide, newest-id first, keyset-paged
— the console's Delivery log tab and any automation that wants to
confirm whether a given monitor's alert reached a destination.

A `deliveries` row is one (incident event, channel) outcome:
`pending` while a delivery attempt is in flight, `delivered` on
success, `dead` once the per-channel attempt ceiling is reached, or
`suppressed` when it was deliberately not sent: the channel's rate
cap dropped it, or it is a `blocked` alert whose run was answered
within the hold (reason in `last_error`). `incident_id`
correlates the row to `GET /api/v1/incidents/{id}` by matching
`(check_id, incident_opened_at)` — it is `null` once the parent
incident has aged out of the 1-year incidents retention window (see
`/privacy`), or for an event type with no incident to correlate with
(e.g. `every-run`). `channel_name`/`channel_kind` are `null` when
the destination channel has since been deleted; the delivery record
itself is not affected by a channel's deletion.

**Pagination** is keyset, on `id` descending. `next_cursor` is an
opaque token; pass it back as `cursor` verbatim to fetch the next
page, and stop when `next_cursor` is `null`. A `cursor` this API
did not issue (or one that has been altered) is rejected with 400
`INVALID_CURSOR`.

**`?since=`** bounds `incident_opened_at` and defaults to the last
30 days; it is clamped to 365 days back regardless of what is
asked for, because deliveries are pruned at 365 days (paired with
the incidents retention window — see `/privacy`) and an earlier
bound finds nothing further anyway.

**Console stability note.** This response is shaped by a console screen:
the fields are the ones that screen draws, in the grouping it draws them
in. It stays published because reading it is a reasonable thing to
automate, and no field will be removed or change meaning without a
version. Fields may be ADDED as the screen grows, so treat unknown keys
as normal rather than as an error.

## Query parameters

- `monitor` string, uuid
- `status` 'pending' | 'delivered' | 'dead' | 'suppressed'
- `since` string, date-time
- `limit` integer
- `cursor` string

## Response `200`

A page of deliveries (may be empty), newest-id first.

- DeliveryPage: The body of `GET /api/v1/deliveries` — one keyset page of deliveries.
  - `deliveries` DeliveryRow[], required
    - `id` integer, required
    - `check_id` string, uuid, required
    - `check_name` string, required: Third-party text: render it as text, never interpret it as markup.
    - `incident_opened_at` string, date-time, required: The opened_at of the incident event this delivery was for.
    - `incident_id` integer, nullable, required: The incident this delivery correlates with, matched by its monitor and opening time. Null once the parent incident has aged out of the 1-year incidents retention window, or for an event type with no incident to correlate with (e.g. `every-run`).
    - `event_type` 'down' | 'recovery' | 'fail' | 'every-run' | 'success' | 'started' | 'blocked' | 'note', required
    - `channel_id` string, uuid, required
    - `channel_name` string, nullable, required: Null when the destination channel has since been deleted — the delivery record itself is unaffected. Third-party text: render it as text, never interpret it as markup.
    - `channel_kind` 'webhook' | 'telegram' | 'discord' | 'slack' | 'msteams' | 'googlechat' | 'ntfy' | 'pushover' | 'email' | 'null', nullable, required: Null under the same condition as `channel_name`.
    - `status` 'pending' | 'delivered' | 'dead' | 'suppressed', required
    - `attempts` integer, required
    - `delivered_at` string, date-time, nullable, required: Set only once status becomes delivered.
    - `last_error` string, required: The most recent delivery failure, or an empty string when none occurred. Third-party text: render it as text, never interpret it as markup.
  - `next_cursor` string, nullable, required: Opaque cursor for the next page; pass back as `?cursor=`. Null when this is the last page.

## Other responses

- `400`: An invalid `monitor`, `status`, `since`, or `cursor` (`INVALID_FILTER` or `INVALID_CURSOR`).
- `401`: Missing or invalid API key.
- `429`: Too many requests. The per-principal fixed-window rate limit has been exceeded. Retry after the number of seconds indicated by the `Retry-After` response header. The problem body's `code` is `RATE_LIMITED`. It names this limiter specifically, so a client can tell it apart from the application-level throttles that also answer 429 with their own codes and their own, much longer, `Retry-After` values (`TEST_PING_RATE_LIMITED`, `TOO_MANY_ATTEMPTS`). Back off globally on `RATE_LIMITED`; back off on the one operation for the others. The principal is the API key for a key-authenticated request and the USER for a browser session, so every tab one person has open shares one budget.

---

[API](https://skmtc.dev/lastping/apis/lastping-management-api.md) · [All operations](https://skmtc.dev/lastping/apis/lastping-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lastping/apis/lastping-management-api/revisions/bbc6eda91c46?raw)
