Pagination
How list responses, page metadata, and truncation flags work across the Parsel API.
Paginated list endpoints in the Parsel API return a uniform { data, metadata } envelope. This page documents the shared shape and how to iterate through pages, using the Billing API as the running example.
The list envelope
Every list endpoint wraps its rows in data and its page state in metadata. For example, GET /billing/invoices/{id}/line_items:
{
"data": [
/* zero or more items */
],
"metadata": {
"current_page": 1,
"page_size": 10,
"total_count": 35,
"total_pages": 4
}
}| Field | Type | Meaning |
|---|---|---|
current_page | integer | The 1-indexed page returned. |
page_size | integer | Items per page. Defaults to 50. |
total_pages | integer | Total number of pages at the current page_size. |
total_count | integer | Total items across all pages. |
Pass ?page=N&page_size=M on the request to control the slice. Defaults are page=1 and page_size=50.
Iterate through every page
When you need every record — for an export, a reconciliation job, or a backfill — increment page until you've consumed all total_pages.
async function fetchAll() {
const all = [];
let page = 1;
while (true) {
const url = new URL("https://api.parsel.app/billing/invoices");
url.searchParams.set("page", String(page));
url.searchParams.set("page_size", "100");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.PARSEL_TOKEN}` },
});
if (!res.ok) throw new Error(`Parsel API ${res.status}`);
const { data, metadata } = await res.json();
all.push(...data);
if (metadata.current_page >= metadata.total_pages) break;
page += 1;
}
return all;
}def fetch_all():
all_rows = []
page = 1
while True:
resp = requests.get(
"https://api.parsel.app/billing/invoices",
headers={"Authorization": f"Bearer {os.environ['PARSEL_TOKEN']}"},
params={"page": page, "page_size": 100},
)
resp.raise_for_status()
body = resp.json()
all_rows.extend(body["data"])
if body["metadata"]["current_page"] >= body["metadata"]["total_pages"]:
break
page += 1
return all_rowsThe same loop pattern works for any paginated endpoint.
Single-resource envelope
Single-resource endpoints (e.g. GET /billing/invoices/{id}) wrap the resource in data with no metadata block:
{
"data": {
"id": "0a6bb80e-3d62-4663-b82f-11840b83850b",
"invoice_number": "INV-VXW8LQ-20260401-20260415",
"status": "OPEN"
}
}Truncation signals
Some endpoints inline a related collection up to a fixed cap and surface a boolean flag when truncated. The Billing API's show endpoint, for instance, inlines up to 250 line items and sets line_items_has_next_page: true when the invoice has more. When that flag is true, switch to the dedicated paginated endpoint (GET /billing/invoices/{id}/line_items) to read the remainder.
The flag and the 250-item cap are specific to billing line items, but the pattern — inline-with-cap plus a *_has_next_page flag — is the convention any future endpoint will follow.
Empty pages
A page value past total_pages returns 200 with data: []. There is no special "out of range" error.