Pagination
Every list endpoint in the Flint API paginates the same way. You request a page with page_size, move forward with page_token, and stop when a response arrives without a next_page_token. Learn the pattern once and it applies to orders, customers, refunds, webhook events, and every other list.
How It Works#
Flint uses cursor pagination. Each page of results includes a token that marks where the list left off, and you hand that token back to get the next page. Three parameters drive everything:
page_sizeintegerHow many records to return per page, from 1 to 100. Defaults to 20 when omitted. Out-of-range values are rejected with a 400, not clamped.
page_tokenstringThe cursor from the previous response's next_page_token. Omit it on the first request.
next_page_tokenstringPresent in a list response when more results exist. Pass it back as page_token to fetch the next page. Absent on the final page.
Fetch the first page of orders:
curl "https://api.withflintpay.com/v1/orders?page_size=2" \
-H "Authorization: Bearer YOUR_API_KEY"
Every list response uses the same envelope: a data array of results, plus next_page_token when there is more to read. The order objects below are trimmed for brevity.
{
"data": [
{
"order_id": "ord_01KWJZAEESGE95VN6GP5J7H6MQ",
"status": "open",
"pricing_amounts": {
"total_money": { "amount": 5444, "currency": "USD" }
},
"created_at": "2026-07-02T18:04:11Z"
},
{
"order_id": "ord_01KWJYV5B8T0AHXQ2M4E7RCN2D",
"status": "paid",
"pricing_amounts": {
"total_money": { "amount": 12000, "currency": "USD" }
},
"created_at": "2026-07-02T17:31:48Z"
}
],
"next_page_token": "Zm9yd2FyZC1vbmx5LW9wYXF1ZS1jdXJzb3I",
"request_id": "e1ce49e4-bf35-4f14-8d06-8a354f276190"
}
Fetching the Next Page#
Send the same request again with page_token set to the token you just received:
curl "https://api.withflintpay.com/v1/orders?page_size=2&page_token=$NEXT_PAGE_TOKEN" \
-H "Authorization: Bearer YOUR_API_KEY"
Two rules keep a pagination run valid:
- Keep the query identical. Use the same endpoint, filters, and sort as the first page; only the token changes. Changing a filter or the sort mid-run returns a 400
INVALID_PAGE_TOKEN. - Use each token once, in order. A cursor points at a position in one specific run. There are no page numbers, and you cannot skip ahead.
page_size is the one exception: it applies per request, so you can read 100 records at a time and drop to 10 later without invalidating the cursor.
When a response comes back without next_page_token, you have reached the end of the list.
Listing Every Record#
With the Node SDK#
The Node SDK manages cursors for you. Every list method returns an async iterable that fetches pages lazily as you consume it:
import { Flint } from "@flintpay/node";
const flint = new Flint({
apiKey: process.env.FLINT_API_KEY!,
});
for await (const order of flint.orders.list({ limit: 100 })) {
console.log(order.orderId, order.status);
}
To work a page at a time instead, await the same call. The SDK names the controls limit and pageToken, and every page exposes data, nextPageToken, and hasMore:
const page = await flint.orders.list({ limit: 100 });
if (page.hasMore) {
const next = await flint.orders.list({
limit: 100,
pageToken: page.nextPageToken,
});
}
Over Direct HTTP#
Loop until the response has no next_page_token:
const orders = [];
let pageToken;
do {
const url = new URL("https://api.withflintpay.com/v1/orders");
url.searchParams.set("page_size", "100");
if (pageToken) url.searchParams.set("page_token", pageToken);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.FLINT_API_KEY}` },
});
if (!response.ok) throw new Error(`List failed: ${response.status}`);
const page = await response.json();
orders.push(...page.data);
pageToken = page.next_page_token;
} while (pageToken);
Bulk reads count against your read rate limits (60 requests per second per key by default). At 100 records per page that leaves comfortable headroom for most exports; add backoff on 429 if you parallelize.
If you are paginating on a schedule to detect new activity, webhooks push those events to you instead: no polling loop, no missed pages.
Sorting#
Lists return newest first by default (created_at, descending). Endpoints that support other orderings accept two parameters:
sort_by: the field to order by. Each resource supports its own set. Orders sort bycreated_at,updated_at,total, oramount_due_money; customers addnameandemail. The API reference lists the fields per endpoint.sort_direction:ascordesc. Defaults todesc.
curl "https://api.withflintpay.com/v1/customers?sort_by=name&sort_direction=asc&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
The sort you choose on the first page is locked in for the rest of the run: the cursor remembers it, and a follow-up request with a different sort is rejected.
Filtering#
Filter parameters combine freely with pagination, and the cursor carries your filters through every page. Most lists support some mix of:
- Date ranges:
created_after,created_before,updated_after,updated_before(RFC 3339 timestamps), plus resource-specific ranges likedue_afteron invoices. - Status filters:
statuson orders and invoices,stateon payment intents, and similar per-resource enums. - Relationships:
customer_id,order_id,payment_intent_id, and other resource links. - Search:
queryfor free-text matching on resources like customers, orders, and products.
curl "https://api.withflintpay.com/v1/orders?status=open&created_after=2026-01-01T00:00:00Z&sort_by=created_at&sort_direction=asc&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
Flint validates query parameters strictly. A parameter the endpoint does not support (a typo, or a filter borrowed from another resource) is rejected with UNSUPPORTED_QUERY_PARAM rather than silently ignored, so mistakes surface on the first request instead of skewing your results.
What You Can Rely On#
- One pattern everywhere. Every list endpoint takes the same pagination parameters and returns the same envelope.
- Stable pages. Ordering is deterministic. A record that existed when you started appears exactly once in a full run: no duplicates, no skips, even while new records are being created.
- New records do not disturb a run in progress. With the default newest-first ordering, records created after your first page will not appear later in the run; start a new run to pick them up. Walking oldest-first (
sort_by=created_at&sort_direction=asc), new records join at the end and your loop reaches them. - Tokens are opaque. The token format is not part of the API contract and can change at any time. Do not parse, construct, or store tokens durably. Treat a token as valid for the run you are in; if one is ever rejected, restart from the first page.
- No total counts. List responses do not include a count of all matching records. If you need running totals, maintain them from webhooks or count during a full run.
Pagination Errors#
Pagination mistakes return HTTP 400 with type: validation_error and the offending parameter in param. See Error Handling for the envelope and retry guidance.
| Code | Cause | Fix |
|---|---|---|
INVALID_PAGE_SIZE | page_size is not an integer from 1 to 100 | Stay within 1 to 100, or omit it for the default of 20 |
INVALID_PAGE_TOKEN | The token is malformed, no longer valid, or the request's filters or sort changed mid-run | Send the token exactly as received, with the original query; otherwise restart from the first page |
INVALID_SORT_BY | The field is not sortable on this endpoint | Use one of the endpoint's documented sort fields |
INVALID_SORT_DIRECTION | A value other than asc or desc | Use asc or desc |
UNSUPPORTED_QUERY_PARAM | The endpoint does not recognize a query parameter | Check the spelling against the endpoint's reference |
Common Mistakes#
- Treating cursors like page numbers. There is no offset parameter and no way to jump to page five. Cursors move forward one page at a time.
- Bookmarking tokens. A
next_page_tokenis meant for the request that follows it, not for storage. To make a long-running sync resumable, persist your own progress marker (for example, thecreated_atof the last record you processed) and resume with an overlappingcreated_afterwindow, de-duplicating by ID. - Mutating the query mid-run. Adding a filter or flipping the sort while reusing an old token returns
INVALID_PAGE_TOKEN. Decide the full query first, then paginate. - Polling lists for changes. If the goal is reacting to new payments or orders, webhooks deliver those events without spending rate limit on repeated runs.
Next Steps#
- Node SDK: auto-pagination with
for awaitand typed list parameters - Rate Limits: the read budgets to plan bulk exports around
- Error Handling: the error envelope and retry guidance
- Webhooks: push notifications instead of polling lists
