Skip to main content

Pagination

Oproto APIs use cursor-based pagination for efficient traversal of large result sets.

How It Works​

List endpoints return paginated responses with a consistent structure:

{
"data": [
{ "id": "comp_abc123", "name": "Acme Corp" },
{ "id": "comp_def456", "name": "Globex Inc" }
],
"pagination": {
"hasMore": true,
"nextCursor": "eyJpZCI6ImNvbXBfZGVmNDU2In0",
"totalCount": 1250
}
}
FieldDescription
dataArray of results for the current page
pagination.hasMoreWhether more results exist
pagination.nextCursorCursor to fetch the next page
pagination.totalCountTotal number of results (when available)

Query Parameters​

ParameterTypeDefaultDescription
limitinteger20Number of results per page (max: 100)
cursorstring—Cursor from previous response

Example Request​

# First page
curl -X GET "https://api.oproto.io/v1/companies?limit=50" \
-H "Authorization: Bearer {access_token}"

# Next page
curl -X GET "https://api.oproto.io/v1/companies?limit=50&cursor=eyJpZCI6ImNvbXBfZGVmNDU2In0" \
-H "Authorization: Bearer {access_token}"

Iterating Through Results​

JavaScript Example​

async function* fetchAllCompanies(limit = 50) {
let cursor = null;

do {
const params = new URLSearchParams({ limit });
if (cursor) params.set('cursor', cursor);

const response = await fetch(
`https://api.oproto.io/v1/companies?${params}`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
);

const { data, pagination } = await response.json();

for (const company of data) {
yield company;
}

cursor = pagination.hasMore ? pagination.nextCursor : null;
} while (cursor);
}

// Usage
for await (const company of fetchAllCompanies()) {
console.log(company.name);
}

Collecting All Results​

async function getAllCompanies() {
const companies = [];

for await (const company of fetchAllCompanies()) {
companies.push(company);
}

return companies;
}

Best Practices​

Use Appropriate Page Sizes​

  • Small pages (10-20): Interactive UIs with quick initial load
  • Medium pages (50): Background sync operations
  • Large pages (100): Bulk data exports

Don't Store Cursors Long-Term​

Cursors are designed for immediate pagination and may expire. Don't persist them across sessions.

Handle Empty Pages​

Always check hasMore rather than assuming empty data means no more results:

// ✅ Correct
while (pagination.hasMore) {
// fetch next page
}

// ❌ Incorrect - may miss results
while (data.length > 0) {
// fetch next page
}

Respect Rate Limits​

When paginating through large datasets, add delays between requests:

async function fetchAllWithDelay(delayMs = 100) {
for await (const item of fetchAllCompanies()) {
process(item);
}
await sleep(delayMs); // Be nice to the API
}

Sorting​

List endpoints support sorting via the sort parameter:

# Sort by name ascending
GET /v1/companies?sort=name

# Sort by creation date descending
GET /v1/companies?sort=-createdAt
PrefixDirection
(none)Ascending
-Descending

Common sortable fields:

  • createdAt — Creation timestamp
  • updatedAt — Last modification timestamp
  • name — Alphabetical by name
note

Sorting is applied before pagination. Changing sort order requires starting from the first page.


Filtering​

Combine pagination with filters to narrow results:

GET /v1/companies?status=active&limit=50
GET /v1/contacts?companyId=comp_abc123&limit=20

See individual endpoint documentation for available filters.