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)
cursorstringCursor 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.