Getting Started with Pagination
Overview
When querying data through the Origami API, endpoints return results in pages rather than dumping the entire dataset at once. Pagination controls how many records are returned per request and which subset of the full result set you receive.
This guide covers general pagination best practices and how pagination works specifically in the Origami API.
General Best Practices
Always paginate
Never assume a query will return a small number of results. Even if you expect 10 records today, future data growth could return thousands. Always implement pagination logic in your client code.
Use a consistent sort order
Pagination relies on a stable ordering of records. Without an explicit sort, the database may return records in a different order between requests, causing duplicates or missed records across pages. Always specify a sort field that produces deterministic ordering (e.g., a unique ID or timestamp + ID combination).
Request only what you need
Smaller page sizes reduce response time, memory usage, and the risk of timeouts. If you only need to check whether records exist, use a small page size (e.g., 1-5) rather than pulling hundreds of records.
Avoid deep offsets when possible
Offset-based pagination becomes slower at high offsets because the database must scan and discard all preceding rows. For very large datasets (hundreds of thousands of records), consider narrowing your filter criteria rather than paginating deep into the result set.
With a maximum page size of 100, retrieving large datasets requires many sequential requests, and performance degrades as startAt grows:
| Request | What happens |
|---|---|
startAt=0&take=100 | Fast. DB fetches first 100 rows. |
startAt=5000&take=100 | DB scans 5,000 rows, discards them, returns the next 100. |
startAt=50000&take=100 | DB scans 50,000 rows, discards them, returns the next 100. Noticeably slower. |
startAt=200000&take=100 | DB scans 200,000 rows to return 100. Can cause timeouts on large tables. |
If you find yourself paginating past startAt=10000, consider:
- Adding a tighter
filter(e.g., date range, status) to reduce the total result set - Sorting by a date field and filtering to a narrower window on each batch (e.g., process one month at a time)
- Using
includeTotalCount=trueon the first call to understand the scale before committing to a full traversal
Handle the last page gracefully
The final page often contains fewer records than the requested page size. Your client code should handle receiving fewer records than expected without treating it as an error.
Origami API Pagination
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
startAt | int | 0 | Zero-based record offset. The index of the first record to return. |
take | int | 30 | Number of records to return per page. |
includeTotalCount | bool | false | When true, the response includes the total number of matching records. |
sort | string | (domain default) | Sort expression applied before pagination. |
Page Size Limits
- Default page size: 30 records
- Maximum page size: 100 records (configurable per tenant via
OrigamiApiMaxQueryResultPageSize)
If you request a take value greater than the configured maximum, it is silently capped to the maximum. The response Take field reflects the actual value used.
Response Structure
{
"List": [
{ "ClaimNumber": "WC-2025-001", "Status": "Open", "TotalIncurred": 45000 },
{ "ClaimNumber": "WC-2025-002", "Status": "Open", "TotalIncurred": 12000 }
],
"Domain": "Claim",
"Columns": "ClaimNumber,Status,TotalIncurred",
"Filter": "Status:eq:Open",
"Sort": "ClaimNumber ASC",
"StartAt": 0,
"Take": 30,
"TotalCount": 142
}| Field | Description |
|---|---|
List | Array of records for the current page |
StartAt | The offset that was used |
Take | The actual page size used (may be less than requested if capped) |
TotalCount | Total matching records (only present when includeTotalCount=true) |
Total Count
By default, TotalCount is omitted from the response for performance reasons (it requires an additional COUNT query against the database). Opt in only when you need it:
GET /api/Claim/Query?filter=Status:eq:Open&startAt=0&take=30&includeTotalCount=true
Use TotalCount to calculate whether more pages exist:
hasMore = (StartAt + Take) < TotalCount
Sorting
The sort parameter accepts one or more field names with direction:
sort=DateOfLoss DESC
sort=Status ASC, ClaimNumber DESC
If no sort is specified, the API applies the domain's default sort order (typically the primary key ascending). Sorting is applied before pagination, so the first page always contains the "first" records according to the sort order.
Pagination Walkthrough
Fetching all records in a loop
To retrieve all matching records, increment startAt by take on each iteration until you receive fewer records than requested:
Page 1:
GET /api/Claim/Query?filter=Status:eq:Open&columns=ClaimNumber,Status&startAt=0&take=50&sort=ClaimID ASC
Page 2:
GET /api/Claim/Query?filter=Status:eq:Open&columns=ClaimNumber,Status&startAt=50&take=50&sort=ClaimID ASC
Page 3:
GET /api/Claim/Query?filter=Status:eq:Open&columns=ClaimNumber,Status&startAt=100&take=50&sort=ClaimID ASC
Continue until List contains fewer than 50 records (or is empty).
Using TotalCount for progress tracking
If you need to display progress or know the total up front, request includeTotalCount=true on the first request:
GET /api/Claim/Query?filter=Status:eq:Open&startAt=0&take=50&includeTotalCount=true
Response includes "TotalCount": 142, so you know there are 3 pages total (50 + 50 + 42).
You can omit includeTotalCount on subsequent pages to reduce database load.
Combining with filters
Pagination works alongside filter and LINQFilter. The filter narrows the dataset first, then pagination applies to the filtered result:
GET /api/Claim/Query?filter=DateOfLoss:gte:2025-01-01&&AdjusterUser:me&startAt=0&take=25&sort=DateOfLoss DESC&includeTotalCount=true
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Not specifying a sort | Records may shift between pages, causing duplicates or gaps | Always include sort with a deterministic field |
| Requesting take=10000 | Silently capped to 100; you only get 100 records and may think that's all | Respect the max page size and loop through pages |
Using includeTotalCount on every page | Adds a COUNT query on every request | Request it once on the first page, then omit |
| Not checking for empty/short pages | Loop runs forever if not checking termination | Stop when List.Length < take or List is empty |
| Changing filter or sort between pages | Breaks result continuity | Keep filter, sort, and take consistent across all pages of the same traversal |
Pseudocode Example
start_at = 0
page_size = 50
all_records = []
while True:
response = api.get(f"/api/Claim/Query?filter=Status:eq:Open&columns=ClaimNumber,Status"
f"&startAt={start_at}&take={page_size}&sort=ClaimID ASC")
all_records.extend(response["List"])
if len(response["List"]) < page_size:
break # Last page reached
start_at += page_sizeUpdated 12 days ago