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:

RequestWhat happens
startAt=0&take=100Fast. DB fetches first 100 rows.
startAt=5000&take=100DB scans 5,000 rows, discards them, returns the next 100.
startAt=50000&take=100DB scans 50,000 rows, discards them, returns the next 100. Noticeably slower.
startAt=200000&take=100DB 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=true on 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

ParameterTypeDefaultDescription
startAtint0Zero-based record offset. The index of the first record to return.
takeint30Number of records to return per page.
includeTotalCountboolfalseWhen true, the response includes the total number of matching records.
sortstring(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
}
FieldDescription
ListArray of records for the current page
StartAtThe offset that was used
TakeThe actual page size used (may be less than requested if capped)
TotalCountTotal 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

MistakeProblemFix
Not specifying a sortRecords may shift between pages, causing duplicates or gapsAlways include sort with a deterministic field
Requesting take=10000Silently capped to 100; you only get 100 records and may think that's allRespect the max page size and loop through pages
Using includeTotalCount on every pageAdds a COUNT query on every requestRequest it once on the first page, then omit
Not checking for empty/short pagesLoop runs forever if not checking terminationStop when List.Length < take or List is empty
Changing filter or sort between pagesBreaks result continuityKeep 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_size

Did this page help you?