Skip to main content
A short checklist of the rules that span every domain. Most integration surprises come from one of the seven items below — skim them once and they stop biting.

The account is fixed by the connection

Every MCP call must carry the X-Sapot-Account-Id header. It is set once, on the connection, and Rails independently enforces that your token is bound to that exact account — a mismatch is rejected with “Access token is not valid for this account”.
The account id is never a tool argument. No tool accepts an account_id parameter — passing one is impossible, and there is no way to reach another account with the same key. One key, one account.

Display id vs database id

Sapot Chat uses two different id spaces, and mixing them is the most common mistake. Conversations and campaigns are addressed by their per-account display id (the number you see in the dashboard URL); contacts are addressed by their database id.
A contact’s id from list_contacts / search_contacts is its database id — feed that straight into update_contact. Do not use a conversation display id as a contact id.

Labels: two separate gotchas

1. Setting labels REPLACES the whole set. add_conversation_labels and add_contact_labels are not additive despite the name — they overwrite the record’s entire label list with exactly what you send. To add one label, read the current labels first and send the union. 2. Titles here, numeric ids there. The two paths take different label representations: Passing a title string where a numeric id is expected (preview_campaign_audience, draft_campaign, update_campaign_draft) silently yields a zero-reach audience — always resolve ids with list_labels first.

Reports use UNIX epoch seconds

The since / until arguments on report tools are UNIX epoch seconds as strings (e.g. "1719792000"), not ISO date strings. This applies to get_report_summary, get_report_timeseries, and get_entity_summary.

Pagination differs by domain

Page size is fixed per domain — there is no per_page argument. Advance with the page argument (1-based) except on messages, which are cursor-paginated by message id.

custom_attributes are MERGED on update

update_contact merges the custom_attributes you send into the existing ones — keys you omit are preserved, not wiped. Send only the keys you want to change; you do not need to read-modify-write the whole object. (This is the opposite of the label tools above, which replace.)
The same “only the fields you pass are changed” rule holds for update_contact, update_knowledge_entry, update_knowledge_profile, and update_campaign_draft. custom_attributes is the one field that deep-merges rather than replaces.

See also

Authentication & Scopes

Default-deny scopes, the 401 scope hint, and the 429 quota response.

REST API

Call the same endpoints directly with the same scoped key.