Read and manage your Zaapi store data programmatically.
The Zaapi Open API Platform lets you access your store's data directly from your own systems, without going through the Zaapi App.
API keys are created and managed by you, inside the Zaapi App. There is no separate application or approval step, and no client secret to exchange.
Warehouse sync or Reporting dashboard.The key is shown once, at creation. Store it somewhere safe before
closing the dialog — you will not be able to see it again. Keys start with
zaapi_. The table on the Developers page only shows a masked value
(zaapi_•••••••• plus the last four characters).
If you lose the secret you don't have to start over. Use Rotate key on that row: it issues a new secret, keeps the name and scopes, and restarts the 6-month expiry.
Keys expire 6 months after they are created or last rotated. The
expiry date is on the Developers page. An expired key is rejected with
401. Rotate before that date if the integration should keep working.
Pass the key in the x-api-key header. There is no bearer token, no
Authorization header, and no token refresh — the key itself is the credential.
A signed request, and the 401 / 403 bodies you get back when the header is
missing, unrecognised, or under-scoped, are shown in the samples panel.
Every request is authenticated and scope-checked before it reaches your store's
data. A request with no x-api-key header, or an unrecognised, inactive,
expired, or deleted key, is rejected with 401. A valid key missing the scope
an endpoint requires is rejected with 403. On success the request is scoped
to your store, so a caller can only ever see their own data.
A key carries the scopes you granted it and acts on your store, so treat it like a password.
Each of these lives on the key's ⋮ menu on Settings → Developers.
Editing renames the key or changes its scopes. The secret itself does
not change, and nothing is shown again — integrations already using the
key keep working. Taking a scope away starts failing those calls with
403 on the next request.
Rotating issues a new secret for the same key and leaves the name and scopes untouched. The new secret is shown once, the same as at creation, and the 6-month expiry restarts today.
The old secret stays valid for two hours. Use that window to deploy
the new one. After two hours, anything still sending the old secret fails
with 401. If your integration reads the key from a secrets store it can
re-read without restarting, update it there and the cutover is immediate.
If two hours is not enough, add a second key with the same scopes, move the integration onto it, confirm it's working, then delete the first.
Rotate when a key has been lost, when it may have been exposed, or before it expires.
Deleting removes a key permanently. It takes effect immediately — the
next request using that key fails with 401 — and it cannot be undone.
Delete when the integration using a key is being retired for good.
Who can do this. Store owners and admins can add, edit, rotate, and delete API keys. The Developers page is not shown to other roles.
Failed requests return the same envelope. 401 and 403 from a missing,
unrecognised, or under-scoped x-api-key header are shown in the samples panel.
Application errors (400, 404, and endpoint-specific failures) look like:
{
"requestId": "<requestId>",
"requestReceivedAt": "<timestamp>",
"storeId": "<id>",
"error": {
"message": "Resource not found",
"statusCode": 404
}
}
Four failures can happen on any request, so they are described here once rather than repeated on every endpoint below.
400 — the request was malformed or failed validation.
Check the request body and query parameters against the endpoint's schema.
error.message names the offending field.
401 — the x-api-key header is missing, or the key is not recognised,
inactive, expired, or has been deleted.
Check that the header is being sent and that the key is correct. If this
started more than two hours after a rotation, the old secret is still
deployed somewhere. If the key was lost, rotate it. If it expired, rotate
it before the next call. If it was deleted, add a new key.
403 — the x-api-key header is valid but was not granted the scope this
endpoint requires.
Check the scope listed on the endpoint. Edit the key on
Settings → Developers to add that scope, or use a different key that
already has it.
404 — no such record in your store, or the key cannot reach it.
Check the ID. A record belonging to another store returns 404, not 403.
409 — optimistic concurrency conflict.
Returned when updating a ticket field whose ticketFieldsUpdatedAt no
longer matches, or when creating a contact whose email or phone is already
on the store. Re-read the resource and retry, or use a different email or
phone.
Treat 401 and 403 as permanent. Retrying with the same key produces the same
result, and a retry loop against 401 will simply generate noise. Both need a
change to the key itself.
Endpoints that can fail in ways specific to what they do — a conflicting write, for example — document those responses individually.
Scopes decide what a key is allowed to do. You choose them when the key is created, and you can change them later with Edit on that key. Rotating a key preserves its scopes. Granting a write scope also grants the matching read scope; removing a read scope removes the matching write scope.
Each resource has a read and write pair. Write scopes for accounts, users, labels, and teams — and the accounts read scope — are defined so keys issued today do not need reissuing when those routes land.
tickets:read — read tickets and ticket field definitionstickets:write — update ticket fields and close ticketsconversations:read — read conversationsconversations:write — mark conversations read and update labelscontacts:read — read contacts and contact field definitionscontacts:write — create contacts and update contact fieldsmessages:read — read messages and internal commentsmessages:write — send messages and create internal commentsaccounts:read — reserved for future read endpointsaccounts:write — reserved for future write endpointsusers:read — read store usersusers:write — reserved for future write endpointslabels:read — read labelslabels:write — reserved for future write endpointsteams:read — read teamsteams:write — reserved for future write endpointsEach endpoint below states the scope it requires.
URLs. Every path is relative to the versioned base URL,
https://openplatform.zaapi.co/v1. Record IDs travel as query parameters on
reads and in the request body on writes, rather than as path segments.
Response envelope. Successful responses wrap the result in data:
{
"requestId": "<requestId>",
"requestReceivedAt": "<timestamp>",
"storeId": "<id>",
"data": { }
}
A single-resource read puts an object in data. A list puts an array in
data, including GET /conversations when you look up one conversation by
id — that call still returns a one-item array. Failed requests use the same
envelope with error instead of data.
Some writes return the updated resource. Others acknowledge the write with
{ "message": "OK" } inside data and do not echo the resource: marking a
conversation read, updating conversation labels, updating a contact field,
sending a plain-text message, and adding an internal comment.
Collections that can grow without bound are cursor-paginated. Walk them
with pageSize and cursor. Do not advance by incrementing page.
Query parameters on paginated endpoints:
pageSize — items per page. Defaults to 20. Maximum is 50, except
GET /messages where the maximum is 15. A value above the endpoint
maximum is rejected with 400. On GET /messages you must send
pageSize — the default of 20 is above that endpoint's maximum.cursor — opaque token from the previous page. Omit it on the first
page. Pass the previous x-pagination-next-cursor value back verbatim —
do not parse, wrap, or reconstruct it. A malformed cursor is rejected
with 400.page — optional integer, defaults to 1. Echoed in
x-pagination-page. It is not how you walk a collection. Sending
page=2 without a cursor does not return the second page.The response body stays a JSON array inside data. Pagination metadata is
returned as headers, not in the body:
x-pagination-pagex-pagination-page-sizex-pagination-totalx-pagination-next-cursor — present only when this page is full
(data.length === pageSize). Omitted on the last page. When it is
present, request the next page with the same filters and pageSize, plus
cursor set to this value. Stop when the header is missing.GET /v1/labels?pageSize=20
# read x-pagination-next-cursor, then:
GET /v1/labels?pageSize=20&cursor=<cursor>
Keep pageSize and every filter the same across pages. Changing them
mid-walk skips or repeats rows.
These endpoints are cursor-paginated:
POST /tickets/list — newest message first, max pageSize 50GET /conversations — most recent last message first, max pageSize 50POST /contacts/list — most recently updated first, max pageSize 50GET /messages — newest first, max pageSize 15GET /messages/internal-comments — newest first, max pageSize 50GET /labels — most recently updated first, max pageSize 50GET /teams — max pageSize 50Small per-store catalogs are returned in full, with no pagination
parameters or x-pagination-* headers:
GET /tickets/fields — all ticket field definitions, display orderGET /contacts/fields — all contact field definitions, display orderGET /users — every user on the storePass expand as a query parameter to resolve related ids into small
objects. Send a comma-separated list (expand=account,assignee) or repeat
the key (expand=account&expand=assignee). Values are exact and
case-sensitive. An unknown value is rejected with 400
(Invalid expand value: …).
Each endpoint accepts only the values listed on it. expand=assignee is
valid on a ticket and rejected on a conversation — assignee lives on
tickets, not conversations.
Two shapes are used:
Replace the id. On tickets and conversations, the id field is omitted and a related object takes its place.
GET /v1/tickets?ticketId=<id>
# { "accountId": "<id>", "assigneeUserId": "<id>" }
GET /v1/tickets?ticketId=<id>&expand=account,assignee
# { "account": { "id": "<id>", "name": "Line Shop", "channel": "line" },
# "assignee": { "id": "<id>", "name": "Nadia Prasert" } }
Add a sibling, or replace a nested id. On messages, accountId and
conversationId stay on the record and account / conversation /
sentBy are added beside them. On an internal comment, expand=createdBy
replaces noteData.createdBy in place. On a team, expand=users turns
each member id into { id, name } when that user exists on the store —
otherwise the raw id is left as-is. On a user, expand=role adds role
{ id, name } and omits roleId.
If a related record cannot be resolved, the expand is skipped for that field and the original id remains.
Allowed expand values:
| Endpoint | Values | Effect |
|---|---|---|
| Ticket reads and writes | account, conversation, assignee, handledBy |
Replaces accountId, conversationId, assigneeUserId, handledBy |
GET /tickets/fields |
accounts |
Adds accounts (name, accountId, channel) |
GET /conversations |
account |
Replaces accountId |
GET /messages |
sentBy, conversation, account |
Adds those objects; ids stay |
GET /messages/internal-comments |
createdBy, conversation, account |
Replaces noteData.createdBy; adds conversation and account |
GET /users |
role |
Adds role; omits roleId |
GET /teams |
users |
Each member becomes { id, name } when known |
Related channel objects are { id, name, channel }. Related users are
{ id, name }, except message sentBy / createdBy, which are
{ userId, name, roleId }.
Timestamps. Resource timestamps are ISO 8601 in UTC.
Errors. See Handle the common errors above for the shared error shape and
the 400, 401, 403, 404, and 409 responses that apply across
endpoints.
As well as calling the API, you can have Zaapi notify your systems when something changes in your store. The events we send are:
Messages
message.received — a message arrives from a contactmessage.sent — a message is sent from ZaapiTickets
ticket.opened — a ticket is opened, including a ticket being reopenedticket.closed — a ticket is closedticket.assigned — a ticket is assigned to someoneticket.unassigned — a ticket's assignee is removedticket.ticket_field_updated — a ticket field is updatedConversations
conversation.label_added — a label is added to a conversationconversation.label_removed — a label is removed from a conversationContacts
contact.contact_field_updated — a contact field is updatedAI
ai.escalated — the AI escalates a chatPayload schemas, endpoint configuration, and signature verification are documented separately.
Individual pieces of work raised against a conversation — listing, reading, updating fields, and closing.
Required scope: tickets:read
Returns a single ticket. Pass expand=account,conversation,assignee,handledBy
to replace those ids with names. Unknown expand values return 400.
| ticketId required | string Example: ticketId=<id> |
| expand | string Example: expand=account,assignee Comma-separated related objects to include. Replaces the matching id
field ( |
| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | object (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/tickets?ticketId=<id>" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}Required scope: tickets:read
Returns a page of tickets in your store, newest message first. With an empty body this is open, non-spam tickets that are not snoozed.
Paginate with pageSize (max 50) and cursor. Pass
expand=account,conversation,assignee,handledBy to replace those ids
with names.
Do not send assigneeUserIds together with unassigned: true.
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 50 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 50 on this endpoint. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Example: expand=account,assignee Comma-separated related objects to include. Replaces the matching id
field ( |
| statuses | Array of strings Items Enum: "open" "closed" Omit to return open tickets only. Send both values for all tickets. | ||||||||
| conversationId | string Restrict to tickets belonging to this conversation. | ||||||||
| accountIds | Array of strings | ||||||||
| assigneeUserIds | Array of strings | ||||||||
| handledByUserIds | Array of strings | ||||||||
| resolutionStatuses | Array of strings Items Enum: "none" "human" "automation" "ai_full_resolution" "ai_partial_resolution" | ||||||||
| unassigned | boolean When true, only tickets with no assignee. Cannot be combined with | ||||||||
| isFollowUp | boolean | ||||||||
| isSpam | boolean Defaults to false (non-spam tickets). | ||||||||
object (AdvancedFilters) Optional extra filters on labels, ticket fields, waiting time, and opened-at. | |||||||||
| |||||||||
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | Array of objects (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||||||||||||||||
{- "statuses": [
- "open"
], - "unassigned": true
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}Required scope: tickets:read
Returns every custom ticket field definition for your store, in display
order. This is a full catalog — it is not paginated. Pass
expand=accounts to include the accounts each field applies to.
| expand | string Value: "accounts" Example: expand=accounts Pass |
| requestId required | string | ||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||
required | Array of objects (TicketField) | ||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/tickets/fields?expand=accounts" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Order number",
- "description": "Customer order reference",
- "displayType": "text",
- "dataType": "string",
- "sortOrder": 0,
- "isRequired": false,
- "active": true,
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}Required scope: tickets:write
Sets or clears one field on a ticket. Send null as newValue to unset it.
Send the ticketFieldsUpdatedAt value you last received for the ticket. If
someone else has changed the fields since then, the request is rejected with
409 and nothing is written — re-read the ticket and try again. Omit
ticketFieldsUpdatedAt when the ticket has never had a field update.
| expand | string Example: expand=account,assignee Comma-separated related objects to include. Replaces the matching id
field ( |
| ticketId required | string |
| ticketFieldId required | string |
| newValue required | any Type depends on the field's |
| ticketFieldsUpdatedAt | string <date-time> The |
| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | object (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||
{- "ticketId": "<id>",
- "ticketFieldId": "<id>",
- "ticketFieldsUpdatedAt": "<timestamp>",
- "newValue": "SO-10482"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}Required scope: tickets:write
Closes a ticket. You can record who handled it, conversion details, and ticket field values in the same request.
| expand | string Example: expand=account,assignee Comma-separated related objects to include. Replaces the matching id
field ( |
| ticketId required | string | ||
| handledBy | string <uuid> The user who handled the ticket. | ||
| isConverted | boolean Whether the conversation led to a conversion. | ||
| totalRevenue | number >= 0 Conversion revenue recorded on close. | ||
| currency | string ISO 4217 code for | ||
object Field values to set when closing, keyed by field id. | |||
| |||
| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | object (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||
{- "ticketId": "<id>",
- "handledBy": "<id>",
- "isConverted": true,
- "totalRevenue": 1890,
- "currency": "THB"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}Channel conversations — finding by conversation or contact, marking read, and applying label changes.
Required scope: conversations:read
Returns a page of conversations. Send conversationId and/or
contactId — at least one is required. A conversationId lookup
returns a one-item page, or 404 if that conversation is not in your
store. contactId pages through that contact's conversations, most
recent last message first.
If both are sent they must refer to the same conversation, otherwise
the response is 404.
Paginate with pageSize (max 50) and cursor. Pass expand=account
to replace accountId with { id, name, channel }. Assignee is not
available on conversations — it lives on tickets.
| conversationId | string Example: conversationId=<id> |
| contactId | string Example: contactId=<id> |
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 50 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 50 on this endpoint. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Value: "account" Example: expand=account Pass |
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||
required | Array of objects (Conversation) | ||||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/conversations?conversationId=<id>&expand=account" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "accountId": "<id>",
- "channel": "line",
- "displayName": "Somchai",
- "contactId": "<id>",
- "referenceId": "<channelConversationId>",
- "labelIds": [
- "<id>"
], - "isSpam": false,
- "unreadMsgCnt": 2,
- "allMsgCnt": 14,
- "lastMsg": {
- "snippet": "Thanks for your help",
- "sentByRecipient": true,
- "sentAt": "<timestamp>"
}, - "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}Required scope: conversations:write
Clears the unread count on the conversation's open ticket. Idempotent —
calling it on an already-read conversation is a no-op. A conversation
with no open ticket returns 404.
| conversationId required | string |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: conversations:write
Applies a label delta. At least one of addLabels or removeLabels is
required. Each array accepts at most 10 ids. Adding a label already
present, or removing one that is absent, is a no-op.
| conversationId required | string |
| addLabels | Array of strings |
| removeLabels | Array of strings |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "addLabels": [
- "<id>"
], - "removeLabels": [
- "<id>"
]
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: contacts:read
Returns a single contact. Use POST /contacts/list to search.
| contactId required | string Example: contactId=<id> |
| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | object (Contact) | ||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/contacts?contactId=<id>" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}Required scope: contacts:write
Creates a contact in your store. Rejected if another contact already has the same primary email or phone. Phone must be E.164.
| firstName required | string <= 50 characters |
| lastName | string <= 50 characters |
string <email> <= 100 characters | |
| phone | string <= 15 characters Primary phone number in E.164 format. |
| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | object (Contact) | ||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||
{- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}Required scope: contacts:read
Returns contacts matching the optional search term and updated-at
window, most recently updated first. Paginate with pageSize (max 50)
and cursor.
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 50 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 50 on this endpoint. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| search | string <= 100 characters Free-text search across name, email, and phone. |
| updatedAfter | string <date-time> Only contacts updated on or after this time. |
| updatedBefore | string <date-time> Only contacts updated before this time. |
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | Array of objects (Contact) | ||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||
{- "search": "somchai"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}Required scope: contacts:read
Returns every custom contact field definition for your store, in display
order. This is a full catalog — it is not paginated. Store limits: 100
fields total, 20 active, and 10 values on a multi-select.
selectOptions is a nested tree with a maximum depth of 4.
| requestId required | string | ||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||
required | Array of objects (ContactField) | ||||||||||||||||||||
Array
| |||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/contacts/fields" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Loyalty tier",
- "displayType": "select",
- "dataType": "string",
- "active": true,
- "sortOrder": 1
}
]
}Required scope: contacts:write
Sets or clears one custom field on a contact. Send null as value to
unset it. Pass the customFieldsUpdatedAt you last read as updatedAt.
If the contact changed in the meantime the write is rejected. Values are
validated against the field definition (regex, max items, select options).
| contactId required | string |
| contactFieldId required | string |
| value required | any Type depends on the field's |
| updatedAt required | string <date-time> The |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "contactId": "<id>",
- "contactFieldId": "<id>",
- "updatedAt": "<timestamp>",
- "value": "VIP"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: messages:read
Returns customer-facing messages, newest first. Events and internal
comments are excluded. Send conversationId and/or ticketId — at
least one is required. When ticketId is set, results are limited to
that ticket's time window (after the previous ticket on the
conversation closed, through this ticket's closedAt if it is closed).
If both ids are sent and do not belong together, the response is 404.
Paginate with pageSize (required, max 15) and cursor. Pass
expand=sentBy,conversation,account to add related names. Those ids
stay on the message.
| conversationId | string Example: conversationId=<id> |
| ticketId | string Example: ticketId=<id> Limit results to this ticket's time window. |
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 15 ] Default: 20 Example: pageSize=15 Items per page. Maximum 15. Required on this endpoint — the global default of 20 exceeds the maximum. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Example: expand=sentBy,account Comma-separated related objects to add beside the message ids.
|
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | Array of objects (Message) | ||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/messages?conversationId=<id>&pageSize=15&expand=sentBy,account" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "accountId": "<id>",
- "sentByRecipient": false,
- "sentAt": "<timestamp>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}Required scope: messages:read
Returns seller-private notes written by agents about a customer on a
conversation, newest first. These do not reach the customer. Paginate
with pageSize (max 50) and cursor. Pass
expand=createdBy,conversation,account to resolve related names.
| conversationId required | string Example: conversationId=<id> |
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 50 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 50 on this endpoint. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Example: expand=createdBy,conversation,account Comma-separated related objects. |
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||
required | Array of objects (InternalComment) | ||||||||||||||||||||
Array
| |||||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/messages/internal-comments?conversationId=<id>&expand=createdBy" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "accountId": "<id>",
- "noteData": {
- "text": "Follow up tomorrow",
- "createdBy": "<id>"
}, - "sentAt": "<timestamp>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}Required scope: messages:write
Saves an internal note on a conversation. Does not reach the customer. Plain text only, not HTML.
| conversationId required | string |
| text required | string |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "text": "Follow up tomorrow"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: messages:write
Sends a plain text message to the customer on their channel. Channel rules apply — for example WhatsApp outside the 24-hour window requires an approved template. Retries send a second message; there is no idempotency key yet.
| conversationId required | string |
| text required | string |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "text": "Thanks for your order!"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: users:read
Returns every user on the store. This is a full list — it is not
paginated. Optionally filter to one userId (a UUID). An unknown
userId returns an empty array. userId on a user may be absent for
invited seats that have not yet been accepted.
Pass expand=role to include { id, name } for the user's role.
Without expand, the response keeps roleId.
| userId | string <uuid> Example: userId=<id> Restrict to this user (UUID). |
| expand | string Value: "role" Example: expand=role Pass |
| requestId required | string | ||||||||||
| requestReceivedAt required | string | ||||||||||
| storeId required | string | ||||||||||
required | Array of objects (User) | ||||||||||
Array
| |||||||||||
curl "https://openplatform.zaapi.co/v1/users?expand=role" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "userId": "<id>",
- "name": "Nadia Prasert",
- "roleId": "<id>"
}
]
}Required scope: labels:read
Returns labels for the store, most recently updated first. Paginate
with pageSize (max 50) and cursor. Pass searchTerm (3–100
characters) to search the label text. Whitespace-only searchTerm is
treated as omitted.
| searchTerm | string [ 3 .. 100 ] characters Example: searchTerm=vip Optional case-insensitive search on the label text (3–100 characters). |
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 50 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 50 on this endpoint. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||
| storeId required | string | ||||||||||||||||
required | Array of objects (Label) | ||||||||||||||||
Array
| |||||||||||||||||
curl "https://openplatform.zaapi.co/v1/labels?pageSize=20" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "label": "VIP",
- "color": "#09c8ab"
}
]
}Required scope: teams:read
Returns teams for the store. Paginate with pageSize (max 50) and
cursor. Pass userId (a UUID) to return only teams containing that
user. Pass expand=users to return member { id, name } instead of
raw user ids. A member with no record on the store stays a raw id.
| userId | string <uuid> Example: userId=<id> Restrict to teams containing this user (UUID). |
| page | integer >= 1 Default: 1 Example: page=1 Echoed in |
| pageSize | integer [ 1 .. 50 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 50 on this endpoint. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Value: "users" Example: expand=users Pass |
| x-pagination-page | integer Example: "1" Current page. |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-total | integer Example: "42" Total number of matching records. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||
| requestReceivedAt required | string | ||||||||
| storeId required | string | ||||||||
required | Array of objects (Team) | ||||||||
Array
| |||||||||
curl "https://openplatform.zaapi.co/v1/teams?pageSize=20&expand=users" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Support",
- "description": "Customer support team",
- "users": [
- "<id>"
]
}
]
}| requestId required | string Gateway request id. Include this when you contact support. | ||||
| requestReceivedAt required | string When the gateway received the request. | ||||
| storeId required | string | ||||
required | object | ||||
| |||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "error": {
- "message": "Resource not found",
- "statusCode": 404
}
}| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | object (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | Array of objects (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}| requestId required | string | ||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||
required | Array of objects (TicketField) | ||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Order number",
- "description": "Customer order reference",
- "displayType": "text",
- "dataType": "string",
- "sortOrder": 0,
- "isRequired": false,
- "active": true,
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}| id required | string | ||||||
| ticketNumber required | string Human-readable ticket number. | ||||||
| channel required | string Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "whatsapp" "widget" … 2 more | ||||||
| isOpen required | boolean | ||||||
| openedAt required | string <date-time> | ||||||
| isSpam required | boolean | ||||||
| contactId required | string | ||||||
| createdAt required | string <date-time> | ||||||
| updatedAt required | string <date-time> | ||||||
| accountId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| conversationId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| closedAt | string <date-time> | ||||||
| reopenedAt | string <date-time> | ||||||
| assigneeUserId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| isFollowUp | boolean | ||||||
object Custom field values keyed by field id, plus any of the system keys
| |||||||
| |||||||
| handledBy | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| ticketFieldsUpdatedAt | string <date-time> Send this back when updating a field so concurrent writes are rejected. | ||||||
{- "id": "<id>",
- "ticketNumber": "<ticketNumber>",
- "accountId": "<id>",
- "conversationId": "<id>",
- "channel": "line",
- "isOpen": true,
- "openedAt": "<timestamp>",
- "assigneeUserId": "<id>",
- "isFollowUp": false,
- "isSpam": false,
- "ticketFields": {
- "<id>": "SO-10482",
- "isConverted": true,
- "salesRevenue": 1890,
- "aiCsat": "5",
- "aiSummary": "Customer asked about shipping"
}, - "ticketFieldsUpdatedAt": "<timestamp>",
- "contactId": "<id>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}| id required | string |
| name required | string |
| channel required | string |
{- "id": "string",
- "name": "Line Shop",
- "channel": "line"
}| id required | string | ||||||
| name required | string | ||||||
| displayType required | string Enum: "text" "multiline_text" "integer" "date" "datetime" "select" "file" "yes_no" … 3 more | ||||||
| dataType required | string Enum: "string" "number" "boolean" "date" | ||||||
| sortOrder required | integer Display order among ticket fields. | ||||||
| isRequired required | boolean | ||||||
| active required | boolean | ||||||
| createdAt required | string <date-time> | ||||||
| updatedAt required | string <date-time> | ||||||
| description | string | ||||||
Array of objects (SelectOption) Options for select fields. | |||||||
Array
| |||||||
| regex | string Validation pattern for free-text values. | ||||||
Array of objects (ShowCondition) When set, this field is only shown if these conditions match. | |||||||
Array
| |||||||
| maxItems | integer Maximum number of values when the field accepts more than one. | ||||||
Array of objects (TicketFieldAccount) Included when | |||||||
Array
| |||||||
{- "id": "<id>",
- "name": "Order number",
- "description": "Customer order reference",
- "displayType": "text",
- "dataType": "string",
- "sortOrder": 0,
- "isRequired": false,
- "active": true,
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}required | string or number |
One of string | |
| children | Array of objects (SelectOption) Nested options for cascading selects. |
{- "value": "apple",
- "children": [
- { }
]
}| ticketFieldId required | string |
| operator required | string Enum: "equals" "not_equals" "greater_than" "less_than" "greater_than_or_equals" "less_than_or_equals" "contains" "not_contains" … 2 more |
| value required | any Value to compare against. Type depends on the field. |
{- "ticketFieldId": "string",
- "operator": "equals",
- "value": null
}| name required | string |
| accountId required | string |
| channel required | string |
{- "name": "Line Shop",
- "accountId": "string",
- "channel": "line"
}| statuses | Array of strings Items Enum: "open" "closed" Omit to return open tickets only. Send both values for all tickets. | ||||||||
| conversationId | string Restrict to tickets belonging to this conversation. | ||||||||
| accountIds | Array of strings | ||||||||
| assigneeUserIds | Array of strings | ||||||||
| handledByUserIds | Array of strings | ||||||||
| resolutionStatuses | Array of strings Items Enum: "none" "human" "automation" "ai_full_resolution" "ai_partial_resolution" | ||||||||
| unassigned | boolean When true, only tickets with no assignee. Cannot be combined with | ||||||||
| isFollowUp | boolean | ||||||||
| isSpam | boolean Defaults to false (non-spam tickets). | ||||||||
object (AdvancedFilters) Optional extra filters on labels, ticket fields, waiting time, and opened-at. | |||||||||
| |||||||||
{- "statuses": [
- "open"
], - "unassigned": true
}object (LabelFilter) | |||||||||||
| |||||||||||
Array of objects (TicketFieldFilter) <= 10 items | |||||||||||
Array (<= 10 items)
| |||||||||||
object (WaitingForReplyFilter) Tickets where the buyer has been waiting at least this many minutes. | |||||||||||
| |||||||||||
object (OpenedAtFilter) | |||||||||||
| |||||||||||
{- "labels": {
- "operator": "contains_any",
- "labelIds": [
- "string"
]
}, - "ticketFields": [
- {
- "ticketFieldId": "string",
- "operator": "equal",
- "value": null,
- "min": null,
- "max": null
}
], - "waitingForReply": {
- "value": 30
}, - "openedAt": {
- "operator": "between",
- "value": "2019-08-24T14:15:22Z",
- "min": "2019-08-24T14:15:22Z",
- "max": "2019-08-24T14:15:22Z"
}
}| operator required | string Enum: "contains_any" "contains_all" "does_not_contain_any" |
| labelIds required | Array of strings [ 1 .. 10 ] items |
{- "operator": "contains_any",
- "labelIds": [
- "string"
]
}| ticketFieldId required | string |
| operator required | string Enum: "equal" "not_equal" "starts_with" "text_contains" "is_one_of" "is_not_one_of" "contains_any" "contains_all" … 6 more |
| value | any A single value, or an array for |
| min | any Lower bound for |
| max | any Upper bound for |
{- "ticketFieldId": "string",
- "operator": "equal",
- "value": null,
- "min": null,
- "max": null
}| operator required | string Enum: "between" "greater_than" "less_than" |
| value | string <date-time> Used with |
| min | string <date-time> Used with |
| max | string <date-time> Used with |
{- "operator": "between",
- "value": "2019-08-24T14:15:22Z",
- "min": "2019-08-24T14:15:22Z",
- "max": "2019-08-24T14:15:22Z"
}| ticketId required | string |
| ticketFieldId required | string |
| newValue required | any Type depends on the field's |
| ticketFieldsUpdatedAt | string <date-time> The |
{- "ticketId": "<id>",
- "ticketFieldId": "<id>",
- "ticketFieldsUpdatedAt": "<timestamp>",
- "newValue": "SO-10482"
}| ticketId required | string | ||
| handledBy | string <uuid> The user who handled the ticket. | ||
| isConverted | boolean Whether the conversation led to a conversion. | ||
| totalRevenue | number >= 0 Conversion revenue recorded on close. | ||
| currency | string ISO 4217 code for | ||
object Field values to set when closing, keyed by field id. | |||
| |||
{- "ticketId": "<id>",
- "handledBy": "<id>",
- "isConverted": true,
- "totalRevenue": 1890,
- "currency": "THB"
}"line"| requestId required | string | ||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||
required | Array of objects (Conversation) | ||||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "accountId": "<id>",
- "channel": "line",
- "displayName": "Somchai",
- "contactId": "<id>",
- "referenceId": "<channelConversationId>",
- "labelIds": [
- "<id>"
], - "isSpam": false,
- "unreadMsgCnt": 2,
- "allMsgCnt": 14,
- "lastMsg": {
- "snippet": "Thanks for your help",
- "sentByRecipient": true,
- "sentAt": "<timestamp>"
}, - "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}| id required | string | ||||||
| channel required | string (Channel) Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "whatsapp" "widget" … 2 more | ||||||
| displayName required | string | ||||||
| contactId required | string | ||||||
| referenceId required | string The channel's own conversation id. | ||||||
| labelIds required | Array of strings | ||||||
| isSpam required | boolean | ||||||
| unreadMsgCnt required | integer | ||||||
| allMsgCnt required | integer | ||||||
| createdAt required | string <date-time> | ||||||
| updatedAt required | string <date-time> | ||||||
| accountId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| emailSubject | string Present on email channels only. | ||||||
| lastRecipientMsgDate | string <date-time> | ||||||
object | |||||||
| |||||||
{- "id": "<id>",
- "accountId": "<id>",
- "channel": "line",
- "displayName": "Somchai",
- "contactId": "<id>",
- "referenceId": "<channelConversationId>",
- "labelIds": [
- "<id>"
], - "isSpam": false,
- "unreadMsgCnt": 2,
- "allMsgCnt": 14,
- "lastMsg": {
- "snippet": "Thanks for your help",
- "sentByRecipient": true,
- "sentAt": "<timestamp>"
}, - "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}| conversationId required | string |
| addLabels | Array of strings |
| removeLabels | Array of strings |
{- "conversationId": "<id>",
- "addLabels": [
- "<id>"
], - "removeLabels": [
- "<id>"
]
}| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | object (Contact) | ||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | Array of objects (Contact) | ||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}| requestId required | string | ||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||
required | Array of objects (ContactField) | ||||||||||||||||||||
Array
| |||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Loyalty tier",
- "displayType": "select",
- "dataType": "string",
- "active": true,
- "sortOrder": 1
}
]
}| id required | string | ||
| fullName required | string | ||
| firstName required | string | ||
| createdAt required | string <date-time> | ||
| updatedAt required | string <date-time> | ||
| lastName | string | ||
string <email> | |||
| phone | string | ||
| secondaryPhones | Array of strings | ||
| secondaryEmails | Array of strings <email> [ items <email > ] | ||
| addresses | Array of strings | ||
| note | string | ||
object Custom field values keyed by field id. | |||
| |||
| customFieldsUpdatedAt | string <date-time> When a custom field was last written. Pass this back as | ||
{- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}| id required | string | ||||
| name required | string | ||||
| displayType required | string Enum: "text" "multiline_text" "integer" "date" "datetime" "select" "file" "yes_no" … 1 more | ||||
| dataType required | string Enum: "number" "boolean" "string" "date" | ||||
| active required | boolean | ||||
| sortOrder required | integer Display order among contact fields. | ||||
| description | string | ||||
Array of objects (SelectOption) Nested options for select fields. Maximum depth 4. | |||||
Array
| |||||
| regex | string Validation pattern for free-text values. | ||||
| maxItems | integer Maximum number of values when the field accepts more than one. | ||||
{- "id": "<id>",
- "name": "Loyalty tier",
- "displayType": "select",
- "dataType": "string",
- "active": true,
- "sortOrder": 1
}| search | string <= 100 characters Free-text search across name, email, and phone. |
| updatedAfter | string <date-time> Only contacts updated on or after this time. |
| updatedBefore | string <date-time> Only contacts updated before this time. |
{- "search": "somchai"
}| firstName required | string <= 50 characters |
| lastName | string <= 50 characters |
string <email> <= 100 characters | |
| phone | string <= 15 characters Primary phone number in E.164 format. |
{- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678"
}| contactId required | string |
| contactFieldId required | string |
| value required | any Type depends on the field's |
| updatedAt required | string <date-time> The |
{- "contactId": "<id>",
- "contactFieldId": "<id>",
- "updatedAt": "<timestamp>",
- "value": "VIP"
}| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | Array of objects (Message) | ||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "accountId": "<id>",
- "sentByRecipient": false,
- "sentAt": "<timestamp>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}| id required | string | ||||||
| conversationId required | string | ||||||
| accountId required | string | ||||||
| sentByRecipient required | boolean | ||||||
| sentAt required | string <date-time> | ||||||
| createdAt required | string <date-time> | ||||||
| updatedAt required | string <date-time> | ||||||
object Present when | |||||||
| |||||||
object Present when | |||||||
| |||||||
| authorUserId | string User id of the teammate who sent the message. | ||||||
object Present when | |||||||
| |||||||
| isSentFromApp | boolean | ||||||
object Channel sender metadata. | |||||||
| |||||||
object Channel-specific message payload. | |||||||
| |||||||
{- "id": "<id>",
- "conversationId": "<id>",
- "accountId": "<id>",
- "sentByRecipient": false,
- "sentAt": "<timestamp>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}| userId required | string |
| name required | string |
| roleId | string |
{- "userId": "<id>",
- "name": "Nadia Prasert",
- "roleId": "string"
}| requestId required | string | ||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||
required | Array of objects (InternalComment) | ||||||||||||||||||||
Array
| |||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "accountId": "<id>",
- "noteData": {
- "text": "Follow up tomorrow",
- "createdBy": "<id>"
}, - "sentAt": "<timestamp>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
]
}| id required | string | ||||||||
| conversationId required | string | ||||||||
| accountId required | string | ||||||||
| createdAt required | string <date-time> | ||||||||
| updatedAt required | string <date-time> | ||||||||
object Present when | |||||||||
| |||||||||
object Present when | |||||||||
| |||||||||
| authorUserId | string | ||||||||
object | |||||||||
| |||||||||
| sentAt | string <date-time> | ||||||||
{- "id": "<id>",
- "conversationId": "<id>",
- "accountId": "<id>",
- "noteData": {
- "text": "Follow up tomorrow",
- "createdBy": "<id>"
}, - "sentAt": "<timestamp>",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}| conversationId required | string |
| text required | string |
{- "conversationId": "<id>",
- "text": "Thanks for your order!"
}| conversationId required | string |
| text required | string |
{- "conversationId": "<id>",
- "text": "Follow up tomorrow"
}| requestId required | string | ||||||||||
| requestReceivedAt required | string | ||||||||||
| storeId required | string | ||||||||||
required | Array of objects (User) | ||||||||||
Array
| |||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "userId": "<id>",
- "name": "Nadia Prasert",
- "roleId": "<id>"
}
]
}| id required | string | ||||
| name required | string | ||||
| userId | string Absent for invited seats that have not yet been accepted. | ||||
| roleId | string Present unless | ||||
object Replaces | |||||
| |||||
{- "id": "<id>",
- "userId": "<id>",
- "name": "Nadia Prasert",
- "roleId": "<id>"
}| requestId required | string | ||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||
| storeId required | string | ||||||||||||||||
required | Array of objects (Label) | ||||||||||||||||
Array
| |||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "label": "VIP",
- "color": "#09c8ab"
}
]
}| id required | string |
| label required | string |
| color required | string |
| fbAdId | string |
| whatsAppAdId | string |
| metaPostId | string |
| whatsAppBroadcastId | string |
| isSystemLabel | boolean |
{- "id": "<id>",
- "label": "VIP",
- "color": "#09c8ab"
}| requestId required | string | ||||||||
| requestReceivedAt required | string | ||||||||
| storeId required | string | ||||||||
required | Array of objects (Team) | ||||||||
Array
| |||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "<timestamp>",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Support",
- "description": "Customer support team",
- "users": [
- "<id>"
]
}
]
}| id required | string |
| name required | string |
| description | string |
Array of strings or RelatedUser (object) User ids by default. When | |
Array One of string | |
{- "id": "<id>",
- "name": "Support",
- "description": "Customer support team",
- "users": [
- "<id>"
]
}