Skip to content

Pagination

All list endpoints return a page at a time, with a cursor that points at the next page. Cursors are stable across inserts — a cursor returned to you yesterday still works today (within the retention window).

Request

GET /v1/customers?limit=50&cursor=eyJpZCI6Ij…
ParamDefaultMaxNotes
limit25100Page size.
cursorOpaque token from the previous response’s next_cursor.

Response

{
"data": [ 50 items ],
"has_more": true,
"next_cursor": "eyJpZCI6IjAxSDJIRzM…"
}

When has_more is false, you’ve reached the end; next_cursor is null.

Walking all pages

cursor = None
while True:
res = api.get("/v1/customers", params={"limit": 100, "cursor": cursor})
for c in res["data"]:
process(c)
if not res["has_more"]:
break
cursor = res["next_cursor"]

Sorting

The default sort is created_at descending (newest first). To change:

GET /v1/customers?sort=created_at:asc

Allowed fields per resource — typically created_at, updated_at, and the resource’s natural key (code, email). The Resources docs list each one.

Filtering

Most list endpoints support filters. Filters are passed as query parameters; their names match the field name on the resource:

GET /v1/subscriptions?status=active,past_due
GET /v1/invoices?customer_id=cus_…&status=open
GET /v1/payments?created_at[gte]=2026-05-01T00:00:00Z
OperatorSyntaxExample
equalsfield=vstatus=active
infield=v1,v2status=active,past_due
greater-equalfield[gte]=vcreated_at[gte]=…
less-thanfield[lt]=vtotal[lt]=100
matches prefixfield[starts]=vemail[starts]=ada@

Filters compose with AND. There’s no OR across fields; for that, fall back to multiple list calls and union client-side.

Cursor stability

A cursor encodes the position of an item in the current sort. As long as that item still exists and the sort hasn’t been changed, the cursor is stable.

  • New items inserted after the cursor: appear on the next page.
  • Items deleted between cursor and current page: skipped silently.
  • Sort changed between calls: cursor invalidated; you’ll get pagination.cursor_invalid.

Cursors don’t expire on a clock — they’re valid as long as the underlying data is queryable. In practice, that’s at least 30 days.

Limits and total counts

Paylera does not return a total count on list responses. Counting all records of a resource is O(n) on a large tenant; we don’t pay that cost on every request. If you need a count, query a report endpoint (GET /v1/reports/*) which is purpose-built for aggregation.

Streaming exports

For very large queries (full customer export, full invoice history), use the export endpoints:

POST /v1/exports
{
"resource": "invoices",
"filters": { "created_at[gte]": "2026-01-01T00:00:00Z" },
"format": "csv"
}

The response includes an export_id. Poll GET /v1/exports/{id} until status: completed, then download from download_url (signed, single-use, 24-hour TTL).

Exports stream, don’t buffer; multi-million-row queries are routine.

Common pitfalls

  • Storing an absolute index (“page 5”). Pages don’t have stable numbers; cursors do.
  • Walking with overlapping filters and treating “no more” as “done for the day.” A cursor that returned has_more: false last hour may have new items now. Restart from null for fresh data.
  • Page-walking in parallel from the same cursor. Cursors are unique to a position, but racing two consumers from the same cursor doesn’t get you parallelism — each gets the same next page. Shard by filter (date range, status) instead.