Pagination
All list endpoints in the Optio Teachable SDK return paginated responses. This guide covers standard pagination, reading pagination metadata, and how to work around the 10,000 record limit using search_after.
Standard pagination
Section titled “Standard pagination”Every list method accepts page and per parameters:
// Fetch page 1 with 20 results per page (default)const { users, meta } = await teachable.v1.users.getList(1, 20);
// Fetch page 2 with 50 results per pageconst { users, meta } = await teachable.v1.users.getList(2, 50);The same pattern applies across all resources:
const { courses } = await teachable.v1.courses.getList(1, 50);const { transactions } = await teachable.v1.transactions.getList(1, 100);const { pricing_plans } = await teachable.v1.pricingPlans.getList(1, 20);Reading pagination metadata
Section titled “Reading pagination metadata”Every list response includes a meta object with full pagination details:
const { users, meta } = await teachable.v1.users.getList(1, 20);
console.log(`Total users: ${meta?.total}`);console.log(`Current page: ${meta?.page}`);console.log(`Total pages: ${meta?.number_of_pages}`);console.log(`Showing records ${meta?.from} to ${meta?.to}`);console.log(`Results per page: ${meta?.per_page}`);Fetching all pages
Section titled “Fetching all pages”For smaller datasets, you can loop through all pages using number_of_pages:
async function getAllUsers() { const allUsers = []; const firstPage = await teachable.v1.users.getList(1, 100); allUsers.push(...firstPage.users);
const totalPages = firstPage.meta?.number_of_pages ?? 1;
for (let page = 2; page <= totalPages; page++) { const { users } = await teachable.v1.users.getList(page, 100); allUsers.push(...users); }
return allUsers;}Paginating beyond 10,000 records
Section titled “Paginating beyond 10,000 records”The Teachable v1 API has a hard limit of 10,000 records for standard page-based pagination on the /users endpoint. If your school has more than 10,000 users, standard pagination will not return records beyond that threshold.
To work around this, use the searchAfter filter, passing the id of the last user returned in the previous page. Teachable will return the next set of records starting after that ID.
async function getAllUsersLargeSchool() { const allUsers = []; let searchAfter: number | undefined = undefined;
while (true) { const { users } = await teachable.v1.users.getList(1, 100, { searchAfter, });
if (users.length === 0) break;
allUsers.push(...users);
// Use the last user's ID as the cursor for the next request searchAfter = users[users.length - 1]?.id;
// If we got fewer results than requested, we've reached the end if (users.length < 100) break; }
return allUsers;}search_after vs standard pagination
Section titled “search_after vs standard pagination”| Standard pagination | search_after | |
|---|---|---|
| Works beyond 10,000 records | ❌ | ✅ |
| Supports random page access | ✅ | ❌ |
| Stable during concurrent writes | ❌ | ✅ |
| Available on all endpoints | ✅ | Users only |
Limitations
Section titled “Limitations”search_afteris only available on/users— other endpoints do not support cursor-based pagination. For large transaction or course datasets, use date range filters to break requests into smaller chunks:
// Break large transaction exports into monthly chunksconst q1 = await teachable.v1.transactions.getList(1, 100, { startDate: '2026-01-01', endDate: '2026-01-31',});-
search_afterexpects a user ID (int32) — pass theidfield of the last user in the previous response, not a page number or token. -
Do not combine
search_afterwithpage— when usingsearch_after, always passpage: 1. Thesearch_aftervalue acts as the cursor and replaces page-based offset entirely.