Skip to content
Ask the docs

Find answers across the QairoPay docs.

Type a question and we'll synthesize an answer from the docs with citations back to the source pages.

Pagination

List endpoints (GET /v1/passes, GET /v1/cards, etc.) are cursor-paginated. Cursor pagination is stable under concurrent inserts and deletes — offset pagination is not — and uses fewer resources at scale.

Shape

Every list response wraps the data:

{
"data": [ /* up to `limit` records */ ],
"has_more": true,
"next_cursor": "eyJpZCI6InBhc3NfMDFIWlgifQ"
}

To fetch the next page, pass the cursor:

Terminal window
curl "https://api.qairopay.com/v1/passes?starting_after=eyJpZCI6InBhc3NfMDFIWlgifQ&limit=100" \
-H "Authorization: Bearer $QAIROPAY_KEY"

When has_more is false, you’ve reached the end of the dataset.

Parameters

ParameterDescription
limitPage size. Default 25, max 100.
starting_afterCursor from a previous response’s next_cursor. Returns records after that cursor.
ending_beforeCursor from a previous response. Returns records before it (for backward iteration).

You cannot pass both starting_after and ending_before in the same request.

Default ordering

Records are returned in descending order by creation time (newest first). To order ascending, pass order=asc. Some list endpoints accept additional sort parameters — check the endpoint’s reference page.

Iterating safely

The TypeScript SDK exposes an async iterator that handles cursors for you. There’s one method per resource — list(...) — and it returns an AsyncListIterator that supports both for-await-of auto-pagination AND page-aware control through .page() / .next().

Auto-paginate with for-await-of

import { QairoPay } from "@qairopay/sdk";
const qp = new QairoPay({ apiKey: process.env.QAIROPAY_KEY! });
// Yields every pass across every page. Cursors are managed internally.
for await (const pass of qp.passes.list({ status: "installed" })) {
console.log(pass.id);
}

The iterator is lazy: breaking out of the loop stops further fetches. If you only need the first 50 records, the SDK only fetches the first page:

let count = 0;
for await (const pass of qp.passes.list({ status: "installed" })) {
if (++count >= 50) break; // exactly one API call total
}

Page-aware control with .page()

When you want to walk pages explicitly — for batching, checkpoint resumption, or backpressure — use .page():

const iter = qp.passes.list({ status: "installed" });
const page1 = await iter.page(); // { data, has_more, next_cursor }
console.log(`got ${page1.data.length} records, has_more=${page1.has_more}`);
if (page1.has_more) {
const page2 = await iter.page(); // advances the cursor
await checkpoint(page2.next_cursor); // persist progress
}

After the iterator is exhausted, .page() returns an empty terminal page ({ data: [], has_more: false, next_cursor: null }) without making a fetch.

Raw HTTP

If you’re not using the SDK, the manual pattern:

let cursor: string | undefined;
do {
const page = await fetch(
`https://api.qairopay.com/v1/passes?limit=100${cursor ? `&starting_after=${cursor}` : ""}`,
{ headers: { Authorization: `Bearer ${KEY}` } },
).then((r) => r.json());
for (const pass of page.data) handle(pass);
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);

Filters

Most list endpoints support filtering. Filters compose with AND and use simple query parameters:

Terminal window
GET /v1/passes?status=active&template_id=tpl_01HZX&created_after=2026-01-01

Date filters use ISO 8601. Comparison operators (gte, lte, etc.) use square-bracket syntax: created[gte]=2026-01-01.

Counts

For performance reasons, list responses do not include a total count. Counting is a separate operation (GET /v1/passes/count?status=active) and is rate-limited more aggressively. If you find yourself fetching all pages just to count them, use the count endpoint instead.