Pagination
Page through list endpoints with cursor-based pagination.
Every VINR list endpoint — payments, invoices, settlements, points transactions, and more — returns results in pages. VINR uses cursor-based pagination rather than numeric page offsets, so results stay stable and fast even while new records are being created underneath you.
Why cursors, not offsetsAsk
Offset pagination (?page=3) re-scans the table on every request and silently skips or duplicates rows when records are inserted or deleted between calls. A cursor is an opaque pointer to a specific position in a stable, descending-by-created_at ordering. Paging forward from a cursor always returns the next slice exactly once, regardless of writes happening concurrently.
Cursors are opaque. Do not parse, decode, or construct them yourself — the format is internal and may change without notice. Treat each cursor as a string you received from VINR and hand back unmodified.
Cursor parametersAsk
List endpoints accept the following query parameters:
Prop
Type
starting_after and ending_before take a resource ID (for example pay_8sQ2...), not a separate token. Use the ID of the last item on the current page to fetch the next page. The two are mutually exclusive — sending both returns a 400.
Response envelopeAsk
List responses share a consistent envelope. The data array holds the resources for the current page; page carries the metadata you need to iterate.
{
"object": "list",
"data": [
{ "id": "pay_8sQ2hT1aZ", "object": "payment", "amount": 1000, "currency": "EUR" },
{ "id": "pay_7rP1gS0bY", "object": "payment", "amount": 2500, "currency": "EUR" }
],
"page": {
"has_more": true,
"next_cursor": "pay_7rP1gS0bY",
"total_estimate": 1842
}
}Prop
Type
total_estimate is a cached approximation for display ("about 1,800 payments"). Never derive a page count or loop bound from it — drive iteration off has_more and next_cursor only.
Iterating fullyAsk
The SDK exposes an async iterator that handles cursors for you. Prefer it over manual paging.
import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });
// Streams every matching payment, fetching pages lazily as you consume them.
for await (const payment of vinr.payments.list({ limit: 100 })) {
console.log(payment.id, payment.amount);
}import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });
let cursor: string | undefined;
do {
const res = await vinr.payments.list({ limit: 100, startingAfter: cursor });
for (const payment of res.data) {
console.log(payment.id, payment.amount);
}
cursor = res.page.has_more ? res.page.next_cursor ?? undefined : undefined;
} while (cursor);# First page
curl https://api.vinr.com/v1/payments?limit=100 \
-H "X-Api-Key: $VINR_SECRET_KEY"
# Next page: pass the last ID from the previous response
curl "https://api.vinr.com/v1/payments?limit=100&starting_after=pay_7rP1gS0bY" \
-H "X-Api-Key: $VINR_SECRET_KEY"When iterating manually, stop when has_more is false. Do not loop on a fixed count, and never assume a page is empty just because it is smaller than limit — only the final page reliably contains fewer items.
Combining with filtersAsk
Cursors are stable across most filters (date ranges, status, customer), so you can page through a filtered set the same way. Keep the filter parameters identical on every request in a sequence — changing a filter mid-iteration invalidates the cursor and returns a 400.
curl "https://api.vinr.com/v1/invoices?status=open&created_after=2026-05-01T00:00:00Z&limit=50" \
-H "X-Api-Key: $VINR_SECRET_KEY"LimitsAsk
- Default page size is 25; maximum is 100. Requesting more than 100 is clamped to 100, not rejected.
- Cursor lifetime is 24 hours. A
next_cursorolder than that returns400 cursor_expired; restart from the first page. - Deep iteration is rate limited like any other call. For large exports (more than ~50,000 records), use the Bulk export jobs instead of paging in a tight loop.
For backfills and reconciliation, page backward in time with the default newest-first order and persist the last processed ID. On the next run, page forward from that ID with starting_after to pick up only what is new.
Next stepsAsk
API Reference
Endpoints, objects, and authentication for the VINR API.
Rate limits
Throughput tiers and how to handle 429 responses while iterating.
Webhooks
React to new records in real time instead of polling list endpoints.