Overview
GET /v1/sync/changes returns a team's changes, page by page, in the order Totem recorded them. Without cursor, it starts an initial sync of every record. With the next_cursor of your last page, it returns what changed since. Each item is the current state of one record, or a removal that tells you to delete it. New kinds of record can appear in the feed in any version: store or skip an item whose kind you do not know, and keep syncing.
Store next_cursor after you apply each page, even an empty one, and send it on your next request. A cursor expires 30 days after the point in the feed it reached; a team you poll every few minutes never gets there.
This page is the reference: the parameters, the item object and the errors. Keep a copy in sync is the guide: how to apply a page, recover from each error, and run a sync client that Totem tests.
GET /
Returns the next page of one team's changes, in the order Totem recorded them: each item is a record's current state or its removal. Without cursor it starts an initial sync of every record; after applying each page, store its next_cursor and send it on the next call, even when the page is empty. A cursor expires 30 days after the point in the feed it reached or, while an initial sync is still running, 30 days after that sync started.
Query parameters
cursorstring 1–512 charsThe
next_cursorof the previous page. Omit it to start an initial sync from the beginning.limitinteger Range 1–100 Default25Items per page, 1–100. Defaults to 25.
Headers
Totem-Teamstring Conditional ID prefixteam_…The team to read, as a
team_…id. Omit it when the key reads one team; it is required when the key reads several (400 team_required). When you send it, it must name a team the key reads: a malformed id gets400 invalid_request, and a team outside the key gets404 team_not_accessible, as does a request without it once the key reads no team at all.
Returns
Returns a list of FeedItem objects.
objectstringAlways
list.dataArray<FeedItem>Changes in the order Totem recorded them. A record can appear more than once during an initial sync.
has_morebooleantruewhen more changes are ready now;falseonce you have caught up.next_cursorstringAlways present, even on an empty page. Store it after applying the page and send it on the next call.
Errors
- 400
team_requiredTeam required - 403
team_state_restrictedTeam restricted - 403
sector_restrictedSector restricted - 404
team_not_accessibleTeam not accessible - 409
resync_requiredResync required - 409
capture_not_readyTeam data is still being prepared - 410
sync_cursor_expiredSync cursor expired - 429
quota_exceededDaily quota exceeded
curl 'https://api.totemstudio.ai/v1/sync/changes?limit=25' \
-H "Authorization: Bearer $TOTEM_API_KEY" \
-H "Totem-Team: $TOTEM_TEAM_ID"{
"object": "list",
"data": [
{
"kind": "lead",
"id": "lead_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXKSF0QbLS8CiUKJpEP34LFQG",
"version": 3,
"object": {
"id": "lead_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXKSF0QbLS8CiUKJpEP34LFQG",
"object": "lead",
"livemode": false,
"team": "team_r9uYrIW4gSFJJ6hZQ9ip1UIpPaAD1CF7NR",
"record_kind": "lead",
"name": "Alex",
"surname": "Rivera",
"email": "alex.rivera@example.com",
"company": "Example Dental",
"phone": "+12025550123",
"phone_status": "valid",
"status": "contacted",
"status_set_by": "ai",
"archive_reason": null,
"archived_from_status": null,
"contact_permissions": {
"do_not_contact": false,
"email_opt_out": false,
"email_opt_out_at": null,
"whatsapp_consent": null,
"provenance": {
"do_not_contact": null,
"email_opt_out": null,
"whatsapp_consent": null
}
},
"merged_into": null,
"transferred_to": null,
"revived_at": null,
"pipeline_cleared": false,
"converted_at": null,
"value_at_conversion": null,
"priority": "high",
"tags": [
"spring-check-up"
],
"board": "brd_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMX3trzsVUZQuHcMSuDh1aRIst",
"stage": "stg_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXWBYWcKwVTY3CRZlVS3oYvs0",
"assignees": [
"mem_h548E7UiYhutHhucVrQCzkM0eaea0"
],
"estimated_value": 1200,
"estimated_value_currency": null,
"source": "meta_lead_ads",
"source_channel": null,
"source_detail": null,
"source_provenance": "member_editable",
"created_at": "2026-09-28T10:15:00.000Z",
"version": 3,
"updated_at": "2026-09-28T10:20:01.250Z"
}
},
{
"kind": "call",
"id": "call_m1pf4APjJWVi3Z7ulyNaW8y3lUi7V",
"version": 5,
"removed": {
"reason": "deleted"
}
},
{
"kind": "lead_response",
"id": "lead_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXKSF0QbLS8CiUKJpEP34LFQG",
"version": 7,
"object": {
"id": "lead_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXKSF0QbLS8CiUKJpEP34LFQG",
"object": "lead_response",
"livemode": false,
"team": "team_r9uYrIW4gSFJJ6hZQ9ip1UIpPaAD1CF7NR",
"last_inbound_at": "2026-10-01T08:58:00.000Z",
"last_outbound_at": "2026-10-01T08:59:30.000Z",
"awaiting_reply": false,
"last_answered_call_at": "2026-09-28T10:17:00.000Z",
"last_whatsapp_delivery_status": "read",
"coverage": {
"conversations": 2,
"calls": 1,
"channels_without_delivery_data": [
"email"
],
"truncated": false
},
"version": 7,
"updated_at": "2026-10-01T08:59:31.040Z"
}
}
],
"has_more": false,
"next_cursor": "eyJ2IjoxLCJ0ZWFtIjoidGVhbV9yOXVZcklXNGdTRkpKNmhaUTlpcDFVSXBQYUFEMUNGN05SIiwiZXBvY2giOjAsInQiOnsicyI6MTc5MDg0NTE3MSwibnMiOjQwMDAwMDAwfSwibiI6IjVkMjJhMTVlM2E5MmNmYTE4NzUzZTVmYzkyZmVjNzI0MjQyNjIzZDE2MGIxZTUxMzRiYTM1ODE5MmUzNzM0ZjEiLCJicyI6bnVsbH01ZPX02"
}One change: the record's current state, or its removal.
Attributes of every variant
kindstringThe kind of the record, such as
leadorcall.Possible valuesleadlead_attributionlead_responseactivitycallconversationbookingboard
versioninteger Range ≥ 1The record's version. Apply the item only when it is greater than the version you hold.
Variants
Each FeedItem is exactly one of these 2 variants.
With object
idstring ID prefixlead_…act_…call_…conv_…bkg_…brd_…Public id of the record; the facets
lead_attributionandlead_responseuse their lead's id.objectone of 8 objectsThe record as it is now.
Matcheskind:leaduses Leadlead_attributionuses LeadAttributionlead_responseuses LeadResponseactivityuses Activitycalluses Callconversationuses Conversationbookinguses Bookingboarduses Board
With removed
idstring ID prefixlead_…act_…call_…conv_…bkg_…brd_…Public id of the removed record.
removedobjectPresent only on a removal.
reasonstringerasedafter an erasure, which is also what deleting a lead, call or conversation in Totem does: erasing a lead erases its calls, conversations and merged duplicates too, while its bookings stay withleadset tonull.deletedfor any other removal. Either way, delete your copy and keep this version, so an older copy delivered later is ignored.Possible valueseraseddeleted
{
"kind": "lead",
"id": "lead_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXKSF0QbLS8CiUKJpEP34LFQG",
"version": 3,
"object": {
"id": "lead_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXKSF0QbLS8CiUKJpEP34LFQG",
"object": "lead",
"livemode": false,
"team": "team_r9uYrIW4gSFJJ6hZQ9ip1UIpPaAD1CF7NR",
"record_kind": "lead",
"name": "Alex",
"surname": "Rivera",
"email": "alex.rivera@example.com",
"company": "Example Dental",
"phone": "+12025550123",
"phone_status": "valid",
"status": "contacted",
"status_set_by": "ai",
"archive_reason": null,
"archived_from_status": null,
"contact_permissions": {
"do_not_contact": false,
"email_opt_out": false,
"email_opt_out_at": null,
"whatsapp_consent": null,
"provenance": {
"do_not_contact": null,
"email_opt_out": null,
"whatsapp_consent": null
}
},
"merged_into": null,
"transferred_to": null,
"revived_at": null,
"pipeline_cleared": false,
"converted_at": null,
"value_at_conversion": null,
"priority": "high",
"tags": [
"spring-check-up"
],
"board": "brd_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMX3trzsVUZQuHcMSuDh1aRIst",
"stage": "stg_r72EfbRNUzuqORFJR0O8HxAwEXd93jGUTYMaMMXWBYWcKwVTY3CRZlVS3oYvs0",
"assignees": [
"mem_h548E7UiYhutHhucVrQCzkM0eaea0"
],
"estimated_value": 1200,
"estimated_value_currency": null,
"source": "meta_lead_ads",
"source_channel": null,
"source_detail": null,
"source_provenance": "member_editable",
"created_at": "2026-09-28T10:15:00.000Z",
"version": 3,
"updated_at": "2026-09-28T10:20:01.250Z"
}
}