Shop Commander · Developers

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)

  1. Initial load: crawl each resource with sort=updated_at and no filter, following next_cursor until has_more is false. Record the newest updated_at you saw.
  2. Incremental: periodically request ?updated_after=<that timestamp>&sort=updated_at. Overlap by a minute (updated_after is strictly greater-than, and clocks drift) and de-duplicate on id.
  3. 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.
  4. Prefer webhooks for latency, updated_after for completeness: subscribe to *.updated events, 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:

Follow next_cursor on each collection. Elsewhere, follow ids: a vehicle carries customer_id; an invoice carries repair_order_id.