Skip to main content

How to handle pagination

A guide to paginating through Tracksuit API results using cursors.

Most Tracksuit API endpoints return their results in pages.

Instead of sending you every row in one giant response, the API gives you a chunk of items plus a token that points at the next chunk. You keep following that token until there are no pages left. This guide shows you the pattern once, and it's identical on every endpoint.

Try it in the browser first. Our interactive API documentation lets you paste in your key, set page_size, and run live requests against every endpoint before you write any code.


The two parameters you control

Pagination is driven by two query parameters, available on every List or Get endpoint:

Parameter

Type

Default

What it does

page_size

integer (1–1000)

1000

Maximum number of items to return in one response.

next_token

string

(none)

The token from the previous response. Omit it on your first call.

Every paginated response carries a matching next_token field:

{   "items": [ ... ],   "next_token": "eyJ2IjogMSwgImsiOiBbIjEwMjk2Il0sInQiOiAiMTAyOTYi..." }

When next_token comes back as null, you've reached the last page.

next_token is the signal, not item count. Page size tells you nothing about whether more pages exist: a page can come back completely full and still be the last one, and a page can come back smaller than page_size and still have more pages to come. Always loop until next_token is null.


The pagination loop

Make your first call with no next_token. Take the next_token from the response, pass it back on the next call, and repeat until it's null.

First call:

curl -G <https://prod.beta.api.gotracksuit.com/v2/category-views> \   -H "Authorization: Bearer YOUR_API_KEY" \   --data-urlencode "page_size=100"

Each subsequent call — same request, plus the token you just received:

curl -G <https://prod.beta.api.gotracksuit.com/v2/category-views> \   -H "Authorization: Bearer YOUR_API_KEY" \   --data-urlencode "page_size=100" \   --data-urlencode "next_token=eyJ2IjogMSwgImsiOiBbIjEwMjk2Il0sInQiOiAiMTAyOTYi..."


Choosing a page size

page_size defaults to 1000, which is also the maximum. The minimum is 1. A value outside 11000 is rejected with a validation error.

Leave page_size at the default for most pulls — bigger pages mean fewer round-trips. Drop it lower only when you want to process results incrementally or keep individual responses small.


Which endpoints paginate

Every endpoint that returns a list supports page_size and next_token:

Endpoint

Paginated?

GET /category-views

✅ Yes

GET /category-views/{id}/funnel

✅ Yes

GET /category-views/{id}/conversion

✅ Yes

GET /category-views/{id}/statements

✅ Yes

GET /category-views/{id}/media-consumption

✅ Yes

GET /category-views/{id}/profile

✅ Yes

GET /category-views/{id} (Get Metadata)

❌ No. Returns one complete object

Many single-brand requests fit comfortably in one page, so you may never see a next_token. Write the loop anyway. It costs nothing when there's only one page, and it future-proofs you against larger result sets.


Treat the token as opaque

The next_token is a signed, encoded cursor. You don't need to understand what's inside it, and you shouldn't try to.

Don't decode, build, or edit the token. Store it and pass it back exactly as received. Tokens are cryptographically signed, so any change makes them invalid (400). The internal format is also subject to change at any time, any integration that parses, rebuilds, or otherwise depends on the token's contents will break eventually. Passing the token straight back is the only future-proof approach.

Keep your other parameters identical across pages. A cursor is tied to the exact request that produced it — same filters, same date range. If you change filters, start_period, metric or any other parameter mid-pagination, the cursor is rejected. To change them, start a fresh pagination from page 1.

Cursors expire after 30 minutes. Page through a result set reasonably promptly. For very large pulls, process each page as you receive it rather than fetching everything before you start work. If a token expires, restart from the first page.


When a cursor is rejected

A bad cursor returns HTTP 400 with a stable error_code you can branch on:

{   "code": "400",   "message": "Pagination cursor has expired",   "error_code": "cursor_expired" }

error_code

What happened

What to do

cursor_expired

The token is older than 30 minutes.

Restart pagination from the first page.

invalid_cursor

The token is malformed or was modified.

Pass the token back exactly as received; don't edit it.

cursor_filters_changed

A filter or query parameter changed since the token was issued.

Keep all parameters identical across pages, or start fresh.

cursor_sort_changed

The sort order changed since the token was issued.

Don't change sorting mid-pagination; start fresh.

cursor_endpoint_mismatch

The token was issued by a different endpoint.

Use a token only against the endpoint that returned it.

cursor_version_unsupported

The token uses an older, retired format.

Restart pagination to get a current token.

The simplest resilient strategy: on any cursor 400, restart the pull from page 1 with your original parameters. See How to: Handle API errors for the full error model.


Common pitfalls

Stopping early. Don't assume a page shorter than page_size means the end — only a null next_token does. Equally, a full page may or may not be the last; page size never tells you either way.

Changing parameters between pages. Filters, date range and sort must stay constant for the life of a cursor. Decide your query up front, then page through it.

Holding a token too long. Tokens last 30 minutes. Don't persist a next_token to resume a job hours later — re-run the pull instead.

Did this answer your question?