API リファレンス
Contacts
Management API: GET /sites/{id}/contacts, POST /sites/{id}/contacts, GET /sites/{id}/contacts/{contactId}, …
List contacts with email marketing status, tags and purchase summary. Filter by status, source, buyers only, or search on email/name/phone; sort by RECENT, SPENT or LAST_ORDER. Errors: FORBIDDEN, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
limitinteger · queryMaximum number of items to return, 1–100. Defaults to 20.
cursorstring · queryOpaque cursor from the previous page's nextCursor. Omit for the first page.
searchstring · querystatus"ALL" | "NEVER_SUBSCRIBED" | "PENDING" | "SUBSCRIBED" | "UNSUBSCRIBED" | "BOUNCED" | "COMPLAINED" · querysource"ALL" | "CHECKOUT" | "ORDER" | "IMPORT" | "FORM" | "MANUAL" | "ADMIN" · querybuyer"ALL" | "BUYERS" · queryBUYERS = has at least one paid order.
sort"RECENT" | "SPENT" | "LAST_ORDER" · query
Response 200
dataobject[]requireddata[].idstringrequireddata[].emailstring | nullrequireddata[].phonestring | nullrequiredE.164.
data[].namestring | nullrequireddata[].emailStatusstringrequiredEmail marketing consent: NEVER_SUBSCRIBED, PENDING, SUBSCRIBED, UNSUBSCRIBED, BOUNCED, COMPLAINED.
data[].sourcestringrequireddata[].createdAtstringrequireddata[].tagsstring[]requireddata[].customerobject | nullrequiredEC purchase summary; null when the contact never bought anything.
data[].customer.ordersCountintegerrequireddata[].customer.totalSpentintegerrequiredMinor units in the website currency (orders in other currencies are not included).
data[].customer.lastOrderAtstring | nullrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403INVALID_CURSOR400
MCP tool contacts_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/contacts" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"id": "contact_example01",
"email": "customer@example.com",
"phone": "+886912345678",
"name": "Alex Chen",
"emailStatus": "SUBSCRIBED",
"source": "FORM",
"createdAt": "2026-10-01T02:30:00.000Z",
"tags": [
"VIP",
"cake-order"
],
"accessStatus": "ACTIVE",
"externalId": null,
"externalIdOrigin": null,
"metadata": {},
"customer": {
"ordersCount": 3,
"totalSpent": 2640,
"lastOrderAt": "2026-10-06T08:00:00.000Z"
}
}
],
"hasMore": false,
"nextCursor": null
}Add one contact by email (source MANUAL). Only set marketingConsent=true when the person has explicitly agreed to receive marketing email; otherwise the contact is created without consent. externalId and metadata let you link the contact to your own system. Errors: FORBIDDEN, VALIDATION, DUPLICATE (email already exists), SUPPRESSED (address is blocked), INVALID_EMAIL, INVALID_PHONE, EXTERNAL_ID_IN_USE.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
emailstringrequirednamestringphonestringAny common format; normalized to E.164 using the store's country.
tagsstring[]notestringmarketingConsentbooleanDefaults to false.
externalIdstringYour system's id for this person (POS, ERP, CRM). Unique per site and set once; it is never used to match or merge contacts.
externalIdOriginstringName of the system the externalId comes from, e.g. "shopify", "hubspot".
metadataobjectIntegration fields to set on the new contact.
Response 201
contactIdstringrequired
Errors
FORBIDDEN403VALIDATION400DUPLICATE409 · email already existsSUPPRESSED422 · address is blockedINVALID_EMAIL400INVALID_PHONE400EXTERNAL_ID_IN_USE422
MCP tool contacts_add
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/contacts" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"customer@example.com","name":"Alex Chen","tags":["cake-order"],"marketingConsent":false}'{
"contactId": "contact_example01"
}Retrieve a contact
/sites/{id}/contacts/{contactId}Get one contact by contactId or by externalId (your system's id) with consent state, sign-in status, note, custom profile attributes, metadata, purchase summary with recent orders, and the last 50 timeline events/messages. Errors: FORBIDDEN, VALIDATION, CONTACT_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
contactIdstring · pathrequiredContact id. Get it from contacts.list.
externalIdstring · queryLook the contact up by your system's id instead of contactId.
Response 200
idstringrequiredemailstring | nullrequiredphonestring | nullrequiredE.164.
namestring | nullrequiredemailStatusstringrequiredEmail marketing consent: NEVER_SUBSCRIBED, PENDING, SUBSCRIBED, UNSUBSCRIBED, BOUNCED, COMPLAINED.
sourcestringrequiredcreatedAtstringrequiredtagsstring[]requiredcustomerobject | nullrequiredcustomer.idstringrequiredcustomer.ordersCountintegerrequiredcustomer.totalSpentintegerrequiredcustomer.lastOrderAtstring | nullrequiredcustomer.acceptsMarketingbooleanrequiredcustomer.countrystring | nullrequiredcustomer.recentOrdersobject[]requiredcustomer.recentOrders[].idstringrequiredcustomer.recentOrders[].orderNumberinteger | nullrequiredcustomer.recentOrders[].statusstringrequiredcustomer.recentOrders[].paidAmountintegerrequiredcustomer.recentOrders[].currencystringrequiredcustomer.recentOrders[].createdAtstringrequiredlocalestring | nullrequiredemailConsentAtstring | nullrequiredemailUnsubscribedAtstring | nullrequirednotestringrequiredaccessStatus"ACTIVE" | "BLOCKED" | "PENDING"requiredSign-in status. BLOCKED: the person cannot sign in to the account portal; orders and emails are unaffected.
externalIdstring | nullrequiredYour system's id for this person; null when none was set.
externalIdOriginstring | nullrequiredWhich system the externalId comes from.
metadataobjectrequiredIntegration fields (flat key/value, up to 50 keys).
suppressedbooleanrequiredOn the tenant suppression list (bounce/complaint/manual block).
profileobject[]requiredCustom attributes collected via forms.
profile[].keystringrequiredprofile[].labelstringrequiredprofile[].valuestringrequiredconsentLogsobject[]requiredconsentLogs[].idstringrequiredconsentLogs[].channelstringrequiredconsentLogs[].actionstringrequiredconsentLogs[].sourcestringrequiredconsentLogs[].createdAtstringrequiredeventsobject[]requiredevents[].idstringrequiredevents[].typestringrequiredevents[].occurredAtstringrequiredevents[].payloadanyrequiredmessagesobject[]requiredmessages[].idstringrequiredmessages[].channelstringrequiredmessages[].kindstringrequiredmessages[].typestringrequiredmessages[].statusstringrequiredmessages[].atstringrequired
Errors
FORBIDDEN403VALIDATION400CONTACT_NOT_FOUND404
MCP tool contacts_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/contacts/{contactId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"id": "contact_example01",
"email": "customer@example.com",
"phone": "+886912345678",
"name": "Alex Chen",
"emailStatus": "SUBSCRIBED",
"source": "FORM",
"createdAt": "2026-10-01T02:30:00.000Z",
"tags": [
"VIP",
"cake-order"
],
"accessStatus": "ACTIVE",
"externalId": null,
"externalIdOrigin": null,
"metadata": {},
"customer": {
"id": "customer_example01",
"ordersCount": 3,
"totalSpent": 2640,
"lastOrderAt": "2026-10-06T08:00:00.000Z",
"acceptsMarketing": true,
"country": "TW",
"recentOrders": [
{
"id": "order_example01",
"orderNumber": 1024,
"status": "PAID",
"paidAmount": 980,
"currency": "TWD",
"createdAt": "2026-10-06T08:00:00.000Z"
}
]
},
"locale": "zh-Hant",
"emailConsentAt": "2026-10-01T02:30:00.000Z",
"emailUnsubscribedAt": null,
"note": "Prefers less sugar.",
"suppressed": false,
"profile": [
{
"key": "birthday",
"label": "Birthday",
"value": "10-20"
}
],
"consentLogs": [
{
"id": "consent_example01",
"channel": "EMAIL",
"action": "SUBSCRIBE",
"source": "FORM",
"createdAt": "2026-10-01T02:30:00.000Z"
}
],
"events": [
{
"id": "evt_example01",
"type": "form.submitted",
"occurredAt": "2026-10-01T02:30:00.000Z",
"payload": {
"formId": "form_example01"
}
}
],
"messages": [
{
"id": "msg_example01",
"channel": "EMAIL",
"kind": "CAMPAIGN",
"type": "campaign",
"status": "DELIVERED",
"at": "2026-10-06T08:00:00.000Z"
}
]
}Update a contact
/sites/{id}/contacts/{contactId}Update a contact's name, tags, owner note, externalId and/or metadata (provide at least one; the others keep their current value). Tags are trimmed, de-duplicated, max 20 × 50 chars. externalId can be set once. metadata is merged: keys you send are set, null deletes a key. Email and phone are identities and are not editable. Errors: FORBIDDEN, VALIDATION, CONTACT_NOT_FOUND, EXTERNAL_ID_ALREADY_SET, EXTERNAL_ID_IN_USE.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
contactIdstring · pathrequiredContact id. Get it from contacts.list.
Request body
namestringDisplay name: printed on certificates and used in email greetings. Empty string clears it (the email address is shown instead). Email and phone are identities and cannot be changed here.
tagsstring[]notestringexternalIdstringSet once; re-sending the same value is a no-op, a different value fails with EXTERNAL_ID_ALREADY_SET.
externalIdOriginstringName of the system the externalId comes from, e.g. "shopify", "hubspot".
metadataobjectIntegration fields. Keys you send are set, a null value deletes that key, keys you omit are kept. Up to 50 keys / 16 KB after merging.
Response 200
contactIdstringrequirednamestring | nullrequiredtagsstring[]requirednotestringrequiredexternalIdstring | nullrequiredmetadataobjectrequired
Errors
FORBIDDEN403VALIDATION400CONTACT_NOT_FOUND404EXTERNAL_ID_ALREADY_SET422EXTERNAL_ID_IN_USE422
MCP tool contacts_update
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/contacts/{contactId}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Alex Chen","tags":["VIP","cake-order"],"note":"Prefers less sugar."}'{
"contactId": "contact_example01",
"name": "Alex Chen",
"tags": [
"VIP",
"cake-order"
],
"note": "Prefers less sugar.",
"externalId": null,
"metadata": {}
}