Docs

Incremental sync

Last updated: 26 Oct 2026

This guide explains how to keep a local copy of an organization's bookings in sync with SERVOS.

Why not just poll orders by date?

GET /admin/v0/orders filters by when the order reached SERVOS (created_at), and the order's updated_at does not change for every relevant transition. In particular, check-in and no-show are recorded on the booking item, not the order, so an order you already fetched can change status without its updated_at moving.

Polling orders by created_from therefore finds new bookings but misses updates to existing ones. Use the events feed for updates.

Recommended loop

  1. Backfill once: page through GET /admin/v0/orders (optionally with created_from) to load history.
  2. Store a cursor: remember the occurred_at of the last event you processed (start it at your backfill time).
  3. Poll GET /admin/v0/events?since=<cursor> on an interval. Events come oldest-first.
  4. For each page, collect the distinct order_id values, then re-fetch each with GET /admin/v0/orders/{order_id} to get its current state.
  5. Advance your cursor to the occurred_at of the last event processed.

A booking's end emits no event: its status and its order's fulfillment_status change when ends_at passes, so also re-fetch the orders of bookings that have ended since your last sync.

cursor = last_synced_at
loop:
  events = GET /admin/v0/events?since=cursor   (page through all pages)
  for order_id in distinct(events[].order_id):
      order = GET /admin/v0/orders/{order_id}
      upsert(order)
  cursor = max(events[].occurred_at)   # if any
  sleep(interval)

Cursor tips

  • Use occurred_at as the cursor, and de-duplicate by event id, two events can share a timestamp, so re-process the boundary timestamp idempotently rather than assuming strict greater-than.
  • Re-fetching the order (rather than trusting the event payload) means you always store the order's authoritative current state, including any changes you might have missed.

Customers

Customer records sync separately and more simply: poll GET /admin/v0/customers?updated_since=<cursor>. Customer edits and marketing consent given or withdrawn bump updated_at, so this is reliable on its own. (Remember: customers are pseudonymous unless your key has customer_data:read.)