Skip to main content
GET /contacts is the export. There is no separate endpoint, and no job to start: you read pages until the cursor is null.

Read every contact

Set limit to 500, then follow next_cursor:
Send next_cursor as cursor on the next request. Stop when has_more is false.

Read only what changed

Send updated_since with the timestamp of your last successful run:
Store the updated_at of the last contact that you read, and use it as the updated_since of your next run. Take a small overlap, for example one minute, so a contact that changed during the run cannot fall between two runs.

Why the order is what it is

Kai returns contacts oldest change first, by updated_at and then by id. An export of 40,000 contacts takes many requests, and your workspace keeps editing contacts while it runs. An edited contact moves to the end of this order, never backward, so the export can see it twice but cannot miss it. A newest-first order has the opposite property, and an export would silently drop rows.

Filters

The filters work together. This request lists the contacts of one teammate that changed today: