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
dataarray on list operations, or adataobject holding the result set on query operations. - A
metaobject that includes apaginationobject.
The meta.pagination object includes the following keys for working with paginated responses:
Keys used for working with paginated results.
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.
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.
Whether this response has another page.
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:
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.
Navigate pages
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.
{ "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
100001to indicate more than 100,000 matches. - It returns
-1if 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:
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.
{ "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:
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.nextcarries the cursor and nothing else, so send the same request body again to that path rather than fetching the URL as aGET. Take only theaftervalue fromnext, 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_pagecounts segments, so a page of five products across three months holds 15 datapoints, not five.
Best practices
For any operation
- Check
has_morerather than inferring from result count.
A response may return fewer results thanper_pageeven when more pages exist. - Mind the
per_pagetrade-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
nextURL directly.
Don't construct it yourself. It already includes your original filters, sort order, and cursor. - Store the
nextURL to resume polling.nextis always returned, even whenhas_moreisfalse. Persist it and use it later to pick up new results without re-scanning from the start. - Send
Skip-Count: truewhen you don't need a count.
Skipping the count query speeds up list responses, especially for large datasets whereestimated_totalwould otherwise return100001.
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_pageagainst your date range.
Page size and the number of time buckets multiply together, and a single page is capped on total datapoints.