Skip to main content
Every list endpoint returns the same envelope and pages with a cursor. Cursors stay correct while new conversations arrive; page offsets would shift.
Treat the cursor as opaque: pass it back exactly as you received it. A cursor that was edited or came from another list returns 400 invalid_request with param: "starting_after".

Order

Lists are newest first. Conversations are ordered by when they started, or by when they last changed when you pass updated_since. Contacts and meetings are ordered by their last change. Events are ordered by when they happened.

Read every page

Sync only what changed

A sync job should not read everything every time. Pass the time of your last run: Values are ISO 8601, for example 2026-10-01T00:00:00Z. A value that isn’t a date returns 400 with param naming the filter. A safe loop:
  1. Note the current time before you start reading. Call it started.
  2. Read every page with updated_since set to the started time of your last successful run.
  3. Upsert each item by its id. The same item can appear in two runs; that’s expected.
  4. Only when every page succeeded, save started for the next run.
Something that changes while you are paging moves to the front of the list and may be skipped in this run, but its new updated_since time is after started, so the next run picks it up. See the CRM sync recipe for a full example.

Lists without paging

GET /webhook_endpoints returns all of a workspace’s endpoints (up to 16) in one page. GET /chat_buttons likewise returns every chat button.