Paginating through large work order lists via API

Pagination behavior on GET /v1/work_orders appears inconsistent when result sets exceed 10k records. Observed:
  • Using page + per_page parameters
  • Records 9,001–10,000: returns as expected
  • Records 10,001+: duplicates from previous pages, gaps, or empty data array
No Link headers present in response. Documentation mentions cursor-based pagination but does not specify parameter names. Questions:
  1. Is offset pagination deprecated for large result sets?
  2. What are the exact cursor parameter names (cursor, after, start_after)?
  3. Is cursor value the id field or a separate token?
  4. Maximum per_page limit?
Tested with:
  • Account: ~45k work orders
  • per_page=100 (also tried 50, 200)
  • Endpoint: /v1/work_orders?status=completed&created_after=2024-01-01
Response structure does not include cursor in body. Example truncated response:
{
  "data": [...],
  "meta": {
    "total": 45231,
    "page": 100,
    "per_page": 100
  }
}
  • Yeah this one tripped me up too when I first looked at it — the docs are technically correct but buried the migration note three paragraphs deep. We moved to cursor pagination for result sets over 10k back in v1.8. The offset parameters still "work" but you'll hit eventual consistency issues at scale. Here's the actual implementation:
    GET /v1/work_orders?cursor=wo_abc123&per_page=100
    # or for first page:
    GET /v1/work_orders?per_page=100
    Key details:
    • Cursor parameter name is cursor (not after)
    • Value is the id of the last record from current page
    • Pass that as cursor for next page
    • When has_more: false, you're done
    Response body changes slightly:
    {
      "data": [...],
      "meta": {
        "cursor": "wo_abc123",
        "has_more": true,
        "per_page": 100
      }
    }
    per_page max is 500 for cursor pagination. The old total field isn't computed for cursors — intentional performance win, since counting 45k rows was part of the problem. I'll flag the docs for a clearer migration callout. Rate limits and pagination guide has more context.
  • Confirmed working. Suggestion: document that has_more replaces need for total — common pattern but not obvious without explicit note.
  • Heads up on a gotcha: if you're filtering by status or date ranges while cursoring, the cursor is scoped to that filter combo. Worth noting if you're changing params mid-pagination — you'll get weird overlaps. Also YMMV but we found per_page=250 to be the sweet spot for throughput vs. memory on our worker nodes. 500 works but response parsing started to feel chunky.
  • Good catch on the filter scoping — that's intentional but definitely underdocumented. The cursor encodes the filter state so you can't accidentally drift between query shapes. I'll add a note about the 250 sweet spot to our internal perf notes. We've seen similar patterns in other high-volume integrations.