API referenceEvents

Read the durable event ledger

Read the durable event ledger (catch-up)

Endpoint
GET https://api.dairo.app/v1/events

Returns your account's durable, ordered event ledger — outbound message lifecycle (email.sent, email.delivered), delivery outcomes (email.bounced, email.complained), and inbound message.received — as a data array of ledger rows, each carrying a monotonic per-partition seq and a partitionKey join key. Events are returned oldest-first (forward catch-up order), so you replay history in the order it happened; order=newest returns the newest first instead. Page forward by passing the previous response's pagination.nextCursor as since (cursor is accepted too): on every non-empty page it names the last event returned, so you can keep polling from it after the last page, and pagination.hasMore says whether more events already exist. limit accepts 1–100 and defaults to 50. wait (0–12 seconds) holds an empty request open until an event arrives, and tail=true returns no events, only the current head cursor to start reading strictly after the newest event. Narrow the stream with inboxId (one partition), type (one event type, e.g. email.bounced), or idempotencyKey (every event threaded from one idempotent send). Each page also reports gaps[] — per-partition seq numbers expected but absent within the returned window (detection is window-local, not whole-history); a non-empty entry means events were lost and can be recovered with a replay. Any other query parameter returns 400. This call is read-only and never re-delivers to webhooks. Requires the events:read scope.

Scope: events:read. Send the API key as a bearer token; Scopes lists what each one reaches.

Request

cURL
curl "https://api.dairo.app/v1/events" \
  -H "Authorization: Bearer $DAIRO_API_KEY"

Query parameters

NameTypeRequiredDescription
sincestringnoOpaque keyset cursor (from a prior pagination.nextCursor) to read forward from.
limitintegernoMaximum number of items in the page. (minimum 1, maximum 100, default 25)
inboxIdstringnoFilter to one inbox partition.
typestringnoFilter to one event type, e.g. email.bounced.
idempotencyKeystringnoReturn every event carrying this loop-wide idempotency key.
waitintegernoLong-poll: when no event is available yet, hold the request up to this many seconds (0..=12, larger values are capped) until one arrives. (minimum 0, maximum 12, default 0)
tailbooleannoReturn no events, only the current head cursor of the (filtered) stream in pagination.nextCursor, to start reading strictly after the newest existing event. (default false)
order"oldest", "newest"nooldest (default, forward catch-up order) or newest (newest first). (default "oldest")

Response

200 Success. Body: application/json, EventLedgerPage.

FieldTypeRequiredDescription
object"list"yesAlways "list" — the unified list envelope discriminator.
dataarray of LedgerEventyesThe ledger events for this page, each with seq/partitionKey join keys. (This is the array named data, not events.)
paginationPaginationyes
gapsarray of LedgerGapyesPer-partition seq gaps detected within this page's window. An empty array means no gap was detected in the returned window — detection is window-local (only the seq range present on this page), not whole-history.
200
{
  "object": "list",
  "data": [
    {
      "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "type": "string",
      "seq": 0,
      "partitionKey": "string",
      "inboxId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "threadId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "idempotencyKey": null,
      "outboundMessageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "messageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "providerMessageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "occurredAt": "2026-10-19T09:30:00Z",
      "createdAt": "2026-10-19T09:30:00Z",
      "data": {}
    }
  ],
  "pagination": {
    "nextCursor": "string",
    "hasMore": true
  },
  "gaps": [
    {
      "partitionKey": "string",
      "missingSeq": [
        0
      ]
    }
  ]
}

Errors

Every error has the same body, {"error": {"type", "code", "message", "param"}}; branch on code. Errors lists every code.

StatusMeaning
400The request is malformed or a value is invalid (invalid_request, invalid_domain, idempotency_key_mismatch).
401The API key is missing or invalid.
403The key lacks the scope this endpoint needs (scope_missing) or may not do this (not_permitted).
404No such resource in this account (resource_not_found).
422The request is well-formed but a field failed validation (validation_failed).
429Rate limit, plan limit, budget or spend cap reached (rate_limited, plan_limit_reached, budget_exceeded, spend_cap_reached).
500An unexpected error; retry with backoff, then contact support (internal_error).

On this page