35 — Developer
Pagination
Every list endpoint paginates the same way: cursor-based, newest first, with an object id as the cursor. No offsets, no page numbers — pages stay stable even while new payments stream in.
The list envelope
List endpoints (GET /v1/payments, /v1/refunds, /v1/events, …) return a common wrapper:
{
"object": "list",
"url": "/v1/payments",
"data": [
{ "id": "pay_8Q2mX4nT1cVb", "object": "payment", "amount": 4900, "currency": "EUR", … },
{ "id": "pay_7Lw9cR5uY2Ze", "object": "payment", "amount": 1250, "currency": "EUR", … }
],
"has_more": true
}
| Field | Meaning |
|---|---|
object | Always "list". |
url | The endpoint that produced this page. |
data | The page of results, newest first. May be empty. |
has_more | true if results exist beyond this page in the direction you're paging. Your loop condition. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
limit | integer | Page size, 1–100. Default 20. |
starting_after | object id | Cursor for the next page: return objects older than this id. Use the last id of the current page. |
ending_before | object id | Cursor for the previous page: return objects newer than this id. Use the first id of the current page. |
starting_after and ending_before are mutually exclusive — pass one or neither. Cursors compose with any endpoint filters (?status=succeeded&limit=50…); keep the filters identical from page to page.
Iterating a full listing
Fetch a page, follow the last id, stop when has_more is false:
# Page 1 — newest 20 succeeded EUR payments
curl -G https://api.finscale.dev/v1/payments \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-d "limit=20" -d "status=succeeded"
# …response ends with "id": "pay_8Q2mX4nT1cVb" and "has_more": true
# Page 2 — everything older than the last id of page 1
curl -G https://api.finscale.dev/v1/payments \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-d "limit=20" -d "status=succeeded" \
-d "starting_after=pay_8Q2mX4nT1cVb"
The same loop in code:
async function listAllPayments() {
const all = [];
let cursor = null;
do {
const params = new URLSearchParams({ limit: "100" });
if (cursor) params.set("starting_after", cursor);
const res = await fetch(`https://api.finscale.dev/v1/payments?${params}`, {
headers: { "Authorization": `Bearer ${process.env.FINSCALE_SECRET_KEY}` }
});
const page = await res.json();
all.push(...page.data);
cursor = page.has_more ? page.data[page.data.length - 1].id : null;
} while (cursor);
return all;
}
To page backwards — say, from a bookmarked payment toward newer ones — use ending_before with the first id you have. has_more then refers to the newer direction.
With ?page=7, a payment created mid-iteration shifts every subsequent page and you skip or double-read rows. An id cursor pins your position in the stream — new arrivals can't move it. That matters when you're reconciling money; for bulk end-of-day work, prefer settlement reports over paging the world.