Pagination
List endpoints return paginated results to keep response sizes manageable. The REST API uses cursor-based pagination, which provides stable results even when the underlying data changes between requests.
How it works
Every list response includes a meta object with pagination details:
Response: 200 Paginated response
{
"data": [
{ "id": "usr_abc123", "email": "[email protected]" },
{ "id": "usr_def456", "email": "[email protected]" }
],
"meta": {
"page": 1,
"perPage": 25,
"totalCount": 142,
"totalPages": 6,
"nextCursor": "eyJpZCI6InVzcl9kZWY0NTYiLCJjcmVhdGVkQXQiOiIyMDI1LTAxLTE1VDA4OjMwOjAwWiJ9"
}
}Events endpoint pagination
Resource endpoints (Users, Courses, etc.) use the
metaobject described above. Event endpoints use thepageInfomodel described below. The two are not interchangeable.
Events & Notifications endpoints use a cursor-only pagination model. Responses include a pageInfo object instead of the meta object:
Response: 200 Events pagination
{
"pageInfo": {
"cursor": "MTUxODM2NTg1Mzk5OQ",
"hasMore": true
},
"events": [ ... ]
}| Field | Type | Description |
|---|---|---|
pageInfo.cursor | string | Opaque cursor to pass as the cursor query parameter for the next page |
pageInfo.hasMore | boolean | Whether additional pages of results are available |
Pass the cursor value as a query parameter to fetch the next page. Continue until hasMore is false.
Pagination parameters
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | integer | No | 1 | Page number to retrieve (1-indexed). |
perPage | integer | No | 25 | Number of records per page. Maximum 100. |
cursor | string | No | — | Opaque cursor for stable forward pagination. Use the nextCursor value from the previous response. |
Pagination meta fields
| Field | Type | Description |
|---|---|---|
page | integer | Current page number |
perPage | integer | Records returned per page |
totalCount | integer | Total records matching the query |
totalPages | integer | Total number of pages |
nextCursor | string | null | Cursor for the next page. null on the last page. |
Iterating through pages
Use the nextCursor value from each response to request the next page:
# First page
curl -s -H "Authorization: Bearer ti_live_a1b2c3d4e5f6g7h8i9j0" \
"https://api.thoughtindustries.com/incoming/v2/users?perPage=50"
# Subsequent pages using the cursor from the previous response
curl -s -H "Authorization: Bearer ti_live_a1b2c3d4e5f6g7h8i9j0" \
"https://api.thoughtindustries.com/incoming/v2/users?perPage=50&cursor=eyJpZCI6InVzcl9kZWY0NTYifQ"async function fetchAllUsers(apiKey) {
const users = [];
let cursor = null;
do {
const url = new URL("https://api.thoughtindustries.com/incoming/v2/users");
url.searchParams.set("perPage", "50");
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers: { "Authorization": `Bearer ${apiKey}` }
});
const { data, meta } = await response.json();
users.push(...data);
cursor = meta.nextCursor;
} while (cursor);
return users;
}import requests
def fetch_all_users(api_key):
users = []
cursor = None
base_url = "https://api.thoughtindustries.com/incoming/v2/users"
while True:
params = {"perPage": 50}
if cursor:
params["cursor"] = cursor
response = requests.get(
base_url,
headers={"Authorization": f"Bearer {api_key}"},
params=params
)
body = response.json()
users.extend(body["data"])
cursor = body["meta"].get("nextCursor")
if not cursor:
break
return usersfunction fetchAllUsers(string $apiKey): array {
$users = [];
$cursor = null;
$baseUrl = "https://api.thoughtindustries.com/incoming/v2/users";
do {
$params = ["perPage" => 50];
if ($cursor) $params["cursor"] = $cursor;
$url = $baseUrl . "?" . http_build_query($params);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = json_decode(curl_exec($ch), true);
$users = array_merge($users, $body["data"]);
$cursor = $body["meta"]["nextCursor"] ?? null;
} while ($cursor);
return $users;
}Best practices
- Use
perPage=50orperPage=100for bulk data exports to minimize round-trips - Prefer cursor-based pagination over page numbers for large datasets — cursors remain stable as records are added or removed
- Store the
totalCountfrom the first response if you need progress indicators - Avoid requesting pages beyond
totalPages— the API returns an emptydataarray
If you only need to check whether records exist, set
perPage=1and inspecttotalCountin the meta object.
Related
- Rate limits — request quotas apply per page request
- Authentication — include credentials on every page request
- Users → List — paginated endpoint example