Connect your systems

Sync contacts

Create and update contacts with several identities, link them to your own user ids, find them again, and merge duplicates.

Last updated

Create a contact with identities

A contact is a person; identities are how messages find them: emails, phones and external ids (your CRM or user id). Custom attributes hold anything your team defines under Settings, Custom attributes.

curl -X POST "$KOMMIO_URL/contacts" \
  -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-user-9917" \
  -d '{"name": "Ana Souza", "company": "Acme", "emails": ["[email protected]"], "phones": ["+5511999990000"],
       "external_ids": ["crm:9917"], "custom_attributes": {"plan": "team", "lifetime_value": 4200}}'
Response 201 Created
{ "id": 77, "name": "Ana Souza", "company": "Acme", "emails": ["[email protected]"], "phones": ["+5511999990000"], "external_ids": ["crm:9917"], "custom_attributes": { "plan": "team", "lifetime_value": 4200 }, "blocked": false, "created_at": "2026-09-22T09:10:00Z" }

Contacts from your website chat

A website visitor becomes a contact only when there is a conversation. Loading a page with the chat creates nothing, whether the visitor is anonymous or signed in on your site: a signed-in visitor's id, name, email and attributes wait with their chat session, and the contact is created, or matched to the one that already carries their external id, with their first message, with a chat an agent starts from Visitors online, or with an email they leave in the offline form. A contact you create here with the same external id (external_ids) is the one a later sign-in or first message lands on, so a sync that wants every user in Contacts creates them through this API rather than waiting for a sign-in. The Contacts page shows people by default; anonymous website visitors, contacts with no email, phone or external id, have their own chip, and the API's audience parameter (people, visitors, all) makes the same split.

Find a contact

# by anything: name, email, phone, external id
curl "$KOMMIO_URL/contacts?q=crm:9917" -H "Authorization: Bearer $KOMMIO_KEY"

# one contact with its identities and history
curl "$KOMMIO_URL/contacts/77" -H "Authorization: Bearer $KOMMIO_KEY"
curl "$KOMMIO_URL/conversations?contact_id=77" -H "Authorization: Bearer $KOMMIO_KEY"

Update, add identities, merge

# update name, company, avatar and custom attributes (the body replaces them: send the full set)
curl -X PUT "$KOMMIO_URL/contacts/77" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Ana Souza", "company": "Acme Ltd", "custom_attributes": {"plan": "business", "lifetime_value": 4200}}'

# add an identity
curl -X POST "$KOMMIO_URL/contacts/77/identities" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "email", "value": "[email protected]"}'

# merge a duplicate into 77 (conversations and identities move; the duplicate is kept as a pointer)
curl -X POST "$KOMMIO_URL/contacts/77/merge" -H "Authorization: Bearer $KOMMIO_KEY" -H "Content-Type: application/json" \
  -d '{"source_id": 91}'

A nightly sync, end to end

sync_contacts.py
import os, requests

BASE, KEY = os.environ["KOMMIO_URL"], os.environ["KOMMIO_KEY"]
H = {"Authorization": f"Bearer {KEY}"}

def upsert(user):
    ext = f"crm:{user['id']}"
    found = requests.get(f"{BASE}/contacts", headers=H, params={"q": ext}).json()["items"]
    fields = {"name": user["name"], "company": user["company"], "custom_attributes": {"plan": user["plan"]}}
    if found:
        # PUT replaces name, company, avatar and attributes; identities are managed separately
        return requests.put(f"{BASE}/contacts/{found[0]['id']}", headers=H, json=fields)
    body = {**fields, "emails": [user["email"]], "external_ids": [ext]}
    return requests.post(f"{BASE}/contacts", headers={**H, "Idempotency-Key": ext}, json=body)

for user in crm.users_changed_since(yesterday):
    r = upsert(user)
    r.raise_for_status()

Contacts you may see follow the same visibility as conversations, and contacts:export gates the CSV export under Contacts. Erasure (DELETE /contacts/{id} with contacts:delete) removes personal data and publishes contact.erased.

Every claim on these pages is covered by a test in the repository, and the API reference is generated from the same contract every server serves at /api/openapi. Corrections to [email protected].