Core Concepts
Pagination
Every list endpoint in LuniPay uses cursor-based pagination. Cursors are stable under inserts and deletes, unlike page numbers, which start skipping or duplicating rows the moment the underlying data changes.
The list envelope
List endpoints return a list object. The shape is the same across every resource — customers, invoices, payments, payment links, checkout sessions, and events:
{
"object": "list",
"data": [
{ "id": "cus_01JRZK...", "object": "customer", "email": "..." }
],
"has_more": true
}data holds the rows. Each row is a full resource object. has_more tells you whether another page exists past the last row in data.
Parameters
| Parameter | Default | Description |
|---|---|---|
| limit | 10 | How many rows to return. Min 1, max 100. |
| starting_after | — | Return rows strictly older than this id. Use the id of the last row on the current page. |
| ending_before | — | Return rows strictly newer than this id. Use the id of the first row on the current page to page backward. |
Pick one cursor
starting_after and ending_before in the same request. The API will return 400 invalid_request_error.Paging forward
The first request has no cursor. Subsequent requests pass the id of the last row on the page you just received. Stop when has_more is false.
# Page 1
curl "https://lunipay.io/api/v1/customers?limit=10" \
-H "Authorization: Bearer sk_test_YOUR_KEY"
# Page 2 — pass the id of the last customer on page 1.
curl "https://lunipay.io/api/v1/customers?limit=10&starting_after=cus_01JRZK..." \
-H "Authorization: Bearer sk_test_YOUR_KEY"Paging backward
Use ending_beforeto walk backward, with the id of the first row on the current page as the cursor. This is handy for a "Prev" button in a paginated UI.
Automatic iteration (SDKs)
The JavaScript and Python SDKs ship with helpers that paginate for you. You get an iterator that fetches pages lazily; you do not have to track cursors by hand.
import lunipay
lunipay.api_key = "sk_test_YOUR_KEY"
for customer in lunipay.Customer.list().auto_paging_iter():
print(customer["email"])import LuniPay from 'lunipay';
const lunipay = new LuniPay('sk_test_YOUR_KEY');
for await (const customer of lunipay.customers.list().autoPagingIter()) {
console.log(customer.email);
}Sort order
Every list endpoint is sorted by created descending. Newest rows come first. Cursors are stable under inserts and deletes — if another row shows up while you are paginating, it will simply appear in a later page the next time you refresh.
Filtering alongside cursors
Most list endpoints accept resource-specific filters — e.g., status=OPEN for invoices, customer=cus_… for payments. Filters combine with cursors cleanly: the cursor walks the filtered set, not the underlying table.