Pagination
Every paginated list endpoint in the Bird API uses the same cursor-based contract: the same request parameters, the same response envelope, the same cursor semantics. Learn it once on GET /v1/email/messages and it applies everywhere.
A small number of bounded collections (for example, billing plans) return a plain {"data": [...]} array with no pagination fields at all — an endpoint either implements this contract in full or not at all, never partially.
Request parameters
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Maximum items per page. Between 1 and 100; defaults to 25. |
| starting_after | string | Cursor from the next_cursor field of a previous response. Returns items immediately after that position. |
| ending_before | string | Cursor from the prev_cursor field of a previous response. Returns items immediately before that position. |
| include_total | boolean | When true, the response includes a total count. Defaults to false. Available on management endpoints only — high-volume data endpoints (messages, events, suppressions) do not accept it. |
Cursors are opaque: they are not resource IDs, they encode the sort position internally, and their format can change at any time. Receive them in responses and pass them back unchanged. A malformed or expired cursor returns a 422 with code E01012 InvalidCursor — restart pagination without a cursor.
Most list endpoints also accept resource-specific sort and order parameters; the per-endpoint reference documents the allowed sort fields. Changing the sort invalidates cursors from the previous sort order.
Response envelope
Codevoorbeeld
{
"data": [{ "...": "..." }],
"next_cursor": "WyIyMDI2LTA2LTEwVDA5OjE0OjAzWiIsICJtc2dfMDFr...",
"prev_cursor": null,
"refresh_cursor": "WyIyMDI2LTA2LTEwVDEyOjAwOjAwWiIsICJtc2dfMDFr...",
"total": 1432
}| Field | Description |
|---|---|
| data | The page of items. |
| next_cursor | Pass back as starting_after to fetch the next page. null when no next page exists — this is the loop-termination signal. |
| prev_cursor | Pass back as ending_before to step backward. null when no previous page exists (always null on the first page). |
| refresh_cursor | A refresh anchor: save it, then pass it back as ending_before later to fetch items that have appeared since this response. Non-null whenever data is non-empty. |
| total | Total items matching the request's filters across all pages. Present only when include_total=true was passed; otherwise null/absent. |
next_cursor and prev_cursor are independent — each is null exactly when its direction has no further page. There is no separate has_more flag; check next_cursor for null.
Walking all pages with curl
Codevoorbeeld
CURSOR=""
while :; do
RESP=$(curl -s "https://us1.platform.bird.com/v1/email/messages?limit=100${CURSOR:+&starting_after=$CURSOR}" \
-H "Authorization: Bearer bk_us1_...")
echo "$RESP" | jq -r '.data[].id'
CURSOR=$(echo "$RESP" | jq -r '.next_cursor // empty')
[ -z "$CURSOR" ] && break
doneThe first request carries no cursor; every subsequent request passes the previous response's next_cursor as starting_after; the loop ends when next_cursor is null.
Pagination in the SDKs
You rarely write that loop by hand. Every Bird SDK exposes list endpoints in two modes: lazy iteration that fetches pages transparently as you consume items, and a single-page accessor for manual cursor control.
Codevoorbeeld
for await (const message of bird.email.list({ status: "bounced" })) {
console.log(message.id);
}Codevoorbeeld
for msg, err := range client.Email.List(context.Background(), bird.EmailListParams{Status: bird.EmailStatusBounced}) {
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id)
}Codevoorbeeld
for message in client.email.list(status="delivered"):
print(message.id)Rate limits
List endpoints share the list rate-limit group, separate from reads of individual resources and from writes — paginating through a large collection never consumes your send or management quota. The lazy iterators above issue one request per page, so a tight loop over a large collection draws down the list group at one request per limit items; use limit=100 for bulk reads.
Related
- Email messages — a representative paginated list endpoint
- SDK concepts — iteration and single-page accessors across the SDKs
- Rate limits — groups, headers, and handling 429s