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

ParameterDefaultDescription
limit10How many rows to return. Min 1, max 100.
starting_afterReturn rows strictly older than this id. Use the id of the last row on the current page.
ending_beforeReturn rows strictly newer than this id. Use the id of the first row on the current page to page backward.

Pick one cursor

Never pass both 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.