Pagination, Filtering & Sync
Opaque cursors, the common filters, and the updated_after cookbook for keeping a mirror in sync.
Pagination
Every list endpoint accepts limit (1–200, default 50) and returns:
{ "object": "list", "data": [ … ], "has_more": true, "next_cursor": "eyJ…" }
Pass cursor=<next_cursor> to fetch the next page. Cursors are opaque and keyed on the sort column plus id, so concurrent inserts never skip or duplicate rows. Offsets are not supported. An invalid or expired cursor returns 400 invalid_cursor — restart the listing without one.
Sorting
Lists default to newest first by created_at. Where supported, sort=updated_at orders by modification time — the order sync crawls need.
Filters
Common filters (see the Reference for each endpoint's full list):
| Endpoint | Filters |
|---|---|
/v1/customers |
q (name/phone/email), email, phone, external_id, updated_after, lifecycle, sort |
/v1/vehicles |
customer, vin, plate, unit_number, q, external_id, updated_after, lifecycle, sort |
/v1/appointments |
scheduled_date_from / scheduled_date_to, status, customer, vehicle, technician, updated_after, sort |
/v1/appointments/availability |
date or start_date / end_date |
/v1/repair-orders |
state, number, customer, vehicle, created_after / created_before, updated_after, sort |
/v1/inventory-items |
q, part_number, product_family, in_stock, active, tire_size / tire_width / tire_aspect_ratio / tire_rim_diameter |
/v1/invoices |
status, doc_type, number, customer, repair_order, business_date_from / business_date_to |
/v1/payments |
settlement_target, repair_order, invoice, business_date_from / business_date_to |
/v1/tasks |
status, assignee, updated_after, sort |
Posted invoices and payments are immutable documents, so they have no updated_after: sync them by inclusive business-date ranges instead.
Filters combine with AND. Phone numbers are matched after normalization, so 4165550123 and (416) 555-0123 find the same customer.
Keeping a mirror in sync (updated_after cookbook)
- Initial load: crawl each resource with
sort=updated_atand no filter, followingnext_cursoruntilhas_moreis false. Record the newestupdated_atyou saw. - Incremental: periodically request
?updated_after=<that timestamp>&sort=updated_at. Overlap by a minute (updated_afteris strictly greater-than, and clocks drift) and de-duplicate onid. - Archives and merges are updates: an archived customer still appears with
"archived": true; a merged customer carries"merged_into": "cus_…"and should be repointed, not deleted. - Prefer webhooks for latency,
updated_afterfor completeness: subscribe to*.updatedevents, and run the incremental crawl on a schedule to catch anything a delivery failure missed. Webhook payloads are thin — always GET the current object.
A 500-vehicle fleet with daily changes touches a handful of pages per crawl — well inside the rate limits.
Related collections
Related collections are paginated rather than embedded without a bound:
GET /v1/repair-orders/{id}/jobsGET /v1/jobs/{id}/partsGET /v1/jobs/{id}/labor
Follow next_cursor on each collection. Elsewhere, follow ids: a vehicle carries customer_id; an invoice carries repair_order_id.