For AI agents and LLMs: a structured documentation index is available at /llms.txt. Every page has a Markdown sibling — append .md to any URL.

Skip to content
Paddle Docs home

Pagination

Paddle uses cursor-based pagination on list operations and on query operations like Explore metrics. Both return the same pagination object, but you work through pages differently.

AI summary

Paddle uses cursor-based pagination for list operations and query operations, with the same pagination object structure but different navigation patterns.

  • • Use has_more and next to iterate reliably through pages—don't rely on estimated_total for exact counts or assume a page is the last one based on result count
  • • For list operations, use the next URL directly as it includes your original filters and sort parameters; for query operations, keep the request body identical and pass only the after cursor as a query parameter
  • • Send Skip-Count: true header on list endpoints to speed up responses when you don't need a total count, and avoid persisting query operation cursors since they expire within 24 hours

Paddle paginates two kinds of operations:

  • List operations return entities in bulk, like transactions, customers, and subscriptions. You walk a live collection, and the Paddle ID of the last entity in a page is the cursor for the next one.
  • Query operations run a query and page through its result set. Explore metrics is the only one today. The query goes in the request body, and the cursor is an opaque token that expires.

Both return the same meta.pagination object, so responses look familiar. However, they work in different ways.

Pagination object

Paginated responses always return:

  • A data array on list operations, or a data object holding the result set on query operations.
  • A meta object that includes a pagination object.

The meta.pagination object includes the following keys for working with paginated responses:

pagination object

Keys used for working with paginated results.

per_page integer

Number of entities per page for this response, or segments per page on query operations. May differ from the number requested if the requested number is greater than the maximum.

next string

URL that marks the starting point of the next page, using the after parameter.

For list operations it also contains the query parameters of the original request, so you can fetch it directly. For query operations it carries only after.

Paddle always returns next, so check has_more to decide whether to request another page.

has_more boolean

Whether this response has another page.

estimated_total integer

Estimated number of entities for this response, or the number of segments on query operations.

On list operations, returns the exact count for datasets of 100,000 or fewer matching entities. Returns 100001 for datasets with more than 100,000 matching entities. Returns -1 when counting is skipped or unavailable.

List operations

List operations are GET requests where your filters and sort order are part of the query string. next is a URL you can fetch as-is.

Page size

Most list operations return 50 results by default and a maximum of 200. Listing transactions returns 30 (the default and maximum). Listing adjustments returns 10 by default and a maximum of 50.

Pass per_page to change the number of results in a page. For example, to get 15 customers per page:

GET /customers?per_page=15

If you request more than the maximum, Paddle returns the maximum. Check meta.pagination.per_page in the response to see how many were returned.

Use the next URL in meta.pagination to fetch the next page. It contains the query parameters from your original request along with an after parameter set to the Paddle ID of the last entity in the current page.

GET /customers
Response (200 OK)
Response (200 OK)
{
"data": [],
"meta": {
"pagination": {
"per_page": 50,
"next": "https://api.paddle.com/customers?after=ctm_01h8441jn5pcwrfhwh78jqt8hk",
"has_more": true,
"estimated_total": 100
}
}
}

Paddle returns results after the cursor, not including it. For example, if a response returns the first ten results, the next URL returns the eleventh entity as the first result.

Check has_more to see if there's another page. When has_more is false, you've reached the last page.

estimated_total gives you an approximate total count, so you can gauge how many requests you'll need:

  • For datasets of 100,000 or fewer entities, it returns the exact count.
  • For larger datasets, it returns 100001 to indicate more than 100,000 matches.
  • It returns -1 if counting is skipped or unavailable.

For reliable iteration over all results, use has_more and next rather than relying on estimated_total for exact counts.

Get previous page

There's no parameter that explicitly returns results before the cursor. Instead, use order_by to reverse the direction.

By default, Paddle returns newest entities first (id[DESC]). Set order_by=id[ASC] to reverse the direction so the next URL takes you backwards from your cursor:

GET /customers?order_by=id[ASC]&after={customer_id}

No results

When there are no results for a request, Paddle returns an empty data array and a meta.pagination object as normal. It doesn't return an error unless the request is invalid.

If you pass a cursor that doesn't exist, Paddle returns a request_error.

Skip the count

If you don't need a total count, send the Skip-Count: true request header on a list endpoint. Paddle skips the count query and returns -1 for estimated_total. This makes responses faster, especially for large datasets.

Use this when you're paging through results with has_more and next and don't need to display or calculate the total.

Query operations

Query operations are POST requests that page through the result set of a query. Explore metrics is the only one today.

Request the next page

After sending your request, check meta.pagination.has_more in the response. When true, send the same request again with the cursor from meta.pagination.next.

POST /metrics/explore
Response (200 OK)
Response (200 OK)
{
"data": {},
"meta": {
"pagination": {
"per_page": 5,
"next": "https://api.paddle.com/metrics/explore?after=eyJ2IjoxLCJxaWQiOiIwMWpuOHg0azJwN3E5ciIsIm9mZiI6NSwicWgiOiJzaGEyNTY6OWYyYzRkMWUiLCJleHAiOjE3ODU1Njc2MDB9",
"has_more": true,
"estimated_total": 42
}
}
}

Take the after value from that URL and send it as a query parameter, with the request body unchanged:

POST /metrics/explore?after={cursor}

Repeat until has_more is false. Paddle returns next on the last page too, so check has_more rather than the presence of next to decide whether to request another page.

The cursor is a base64-encoded string that records which query it belongs to and where you are in the results. What it holds can change, so don't decode it, construct it yourself, or send it with a different query.

Differences to list operations

Four things work differently:

  • Your query stays in the request body.
    meta.pagination.next carries the cursor and nothing else, so send the same request body again to that path rather than fetching the URL as a GET. Take only the after value from next, not the whole URL.
  • The cursor is tied to the query that produced it.
    Change any part of the body between pages and the cursor no longer matches, so Paddle returns an error. Start again from the first page when you want to change the query.
  • Cursors expire.
    They point at a cached result set rather than a permanent entity ID, so you can't store one and come back to it later. Request the first page again instead.
  • You page segments, not entities.
    A segment is one combination of the dimensions you grouped by, and each carries its own full result. per_page counts segments, so a page of five products across three months holds 15 datapoints, not five.

Best practices

For any operation

  • Check has_more rather than inferring from result count.
    A response may return fewer results than per_page even when more pages exist.
  • Mind the per_page trade-off.
    Larger pages mean fewer requests but slower responses, especially for larger entities.
  • Cache results in client-side apps.
    Short-lived caches reduce load on both your app and the Paddle API.

For list operations

  • Use the next URL directly.
    Don't construct it yourself. It already includes your original filters, sort order, and cursor.
  • Store the next URL to resume polling.
    next is always returned, even when has_more is false. Persist it and use it later to pick up new results without re-scanning from the start.
  • Send Skip-Count: true when you don't need a count.
    Skipping the count query speeds up list responses, especially for large datasets where estimated_total would otherwise return 100001.

For query operations

  • Keep the request body identical while paging.
    The cursor is tied to the query that produced it, so any change to the body invalidates it.
  • Don't persist cursors.
    Cursors last an hour from when they're issued, extended each time you use it, up to 24 hours. Store the query instead and run it again from the first page when you need fresh results.
  • Size per_page against your date range.
    Page size and the number of time buckets multiply together, and a single page is capped on total datapoints.

Was this page helpful?