Pagination
The limit/offset envelope, ordering guarantees, and how to walk pages without losing items.
List endpoints paginate with limit and offset query parameters and return a counted envelope:
{
"runs": [ { "id": "0196a3f2-…", "...": "…" } ],
"total": 412,
"limit": 50,
"offset": 0
}
The array key matches the resource (runs, programs); total is the count of everything matching your filters, not just this page.
| Endpoint | Default limit |
Max |
|---|---|---|
GET /runs |
50 | 1000 |
GET /programs |
100 | 1000 |
GET /programs/{program_id}/runs |
100 | 1000 |
Ordering#
Lists are ordered by creation time, newest first. The order of a given row never changes — created_at is immutable, and updates don't reorder a list. Ids are UUIDv7, so sorting by id and sorting by creation time agree.
Walking pages#
Advance offset by the number of items received until you've seen total. Two things to know:
- Drift produces duplicates, never gaps. Newest-first ordering means a program run created mid-walk pushes later pages down: you may see an item twice, but you can't skip one. Process pages with an upsert keyed on
idand drift is invisible. totalcan change between pages as new items arrive. Compare against the most recent response'stotal, not the first one.
For keeping a store continuously current, don't re-walk everything — use the updated_since high-water-mark loop on the Open REST API page.
The exception: webhook deliveries#
GET /webhooks/{webhook_id}/deliveries uses a time cursor instead of an offset: limit (default 50, max 200) plus an optional before timestamp. Results come newest-first; to go further back, pass the oldest created_at you've seen as before. There is no total — deliveries are an append-heavy log, and the cursor avoids the drift offset pagination would suffer there.