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. To be notified when something changes, subscribe to webhooks.
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": "1757580000000",
"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 — the request conflicts with current store data.
Returned when updating a ticket or contact field with an out-of-date
resource timestamp, or when creating a contact whose email or phone is
already on the store. Re-read the resource and retry with its latest field
timestamp, 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 are defined so keys issued today do not need reissuing when those routes land.
tickets:read — read tickets and ticket field definitionstickets:write — assign tickets, update ticket fields, and close ticketsconversations:read — read conversationsconversations:write — mark conversations read and update labelscontacts:read — read and search contacts, and read contact field definitionscontacts:write — create contacts and update contact fieldsmessages:read — read, search messages, and read internal commentsmessages:write — send messages, including email and WhatsApp templates, and create internal commentsaccounts:read — read channel accounts and WhatsApp message templatesaccounts: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.
Rate limits. Each key is limited to:
Response envelope. Successful responses wrap the result in data:
{
"requestId": "<requestId>",
"requestReceivedAt": "1757580000000",
"storeId": "<id>",
"data": { }
}
requestReceivedAt is Unix time in milliseconds, as a string. In
JavaScript:
new Date(Number(body.requestReceivedAt))
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, sending an email, sending a WhatsApp
template, and adding an internal comment.
Collections that can grow without bound are cursor-paginated. Walk them
with pageSize and cursor.
Query parameters on paginated endpoints:
pageSize — items per page. Optional. Defaults to 20, except
GET /messages where it defaults to 15. Maximum is 100 on every
endpoint that accepts pageSize. GET /messages/search and
GET /contacts/search do not accept pageSize; each page contains 10
results. A value above 100, below 1, or that is not a
whole number is rejected with 400.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.The response body stays a JSON array inside data. Pagination metadata is
returned as headers, not in the body:
x-pagination-page-sizex-pagination-next-cursor — present when another page may be available.
A page can contain fewer than pageSize items and still include this
header. When it is present, request the next page with the same filters
and pageSize, plus cursor set to this value. Stop only when the header
is missing.First page:
GET /v1/labels?pageSize=20
Next page — same filters and pageSize, plus the cursor from
x-pagination-next-cursor:
GET /v1/labels?pageSize=20&cursor=<cursor>
Keep pageSize and every filter the same across pages. Changing them
mid-walk can reject the cursor, skip rows, or repeat rows.
These endpoints are cursor-paginated. All of them accept a pageSize of
up to 100:
POST /tickets/list — newest message firstGET /conversations — most recent last message firstPOST /contacts/list — most recently updated first without search;
relevance first with searchGET /messages — newest first, default pageSize 15GET /messages/internal-comments — newest firstGET /labels — most recently updated firstGET /teams — oldest firstGET /accounts — oldest firstGET /messages/search and GET /contacts/search are also cursor-paginated,
in relevance order. Each page contains 10 results. Do not send pageSize.
Pass cursor from x-pagination-next-cursor to continue.
Small 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 storeGET /accounts/whatsapp-templates returns at most 100 templates for one
WhatsApp account. It does not take pageSize or cursor.
Pass 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, conversationId
stays on the record and account / conversation / sentBy are added
beside it. There is no accountId on the message. 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. conversationId stays |
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. Envelope
requestReceivedAt is Unix milliseconds as a string — see Response
envelope above.
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.
Install the @zaapi/open-api-mcp server so an assistant such as Claude can call this API on your store. Each operation becomes a tool. The server sends the API key you configure, and that key's scopes decide what the assistant can do.
The assistant starts the server with npx, which comes with
Node.js. Install Node.js on the machine that runs
the assistant.
Claude.The key is shown once. Put it in the config below, on the machine that runs the assistant. The same rules in Keep the key safe apply.
Claude Desktop runs the server on your machine.
That opens claude_desktop_config.json. Edit Config creates the file
when it is missing.
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonAdd a zaapi entry. If mcpServers already lists other servers, add
zaapi next to them.
{
"mcpServers": {
"zaapi": {
"command": "npx",
"args": ["-y", "@zaapi/open-api-mcp"],
"env": {
"ZAAPI_API_KEY": "zaapi_…"
}
}
}
}
Save the file. Quit Claude from the menu, then open it again so it reloads the config. In a new chat, Zaapi is listed with the assistant's tools.
| Variable | Required | What to set |
|---|---|---|
ZAAPI_API_KEY |
Yes | The key from Settings → Developers. The server sends it as x-api-key. Keys start with zaapi_. |
ZAAPI_BASE_URL |
No | Defaults to https://openplatform.zaapi.co. Set https://openplatform-staging.zaapi.co to call staging. |
A missing, expired, or under-scoped key fails the tool call the same way a direct request fails. See Handle the common errors.
Other assistants that accept an mcpServers config use the JSON in
Add the server in Claude Desktop.
Individual pieces of work raised against a conversation — listing, reading, assigning, 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": "1757580000000",
- "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 100) and cursor. Pass
expand=account,conversation,assignee,handledBy to replace those ids
with names.
Do not send assigneeUserIds together with unassigned: true.
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| 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. Sent on its
own, returns the conversation's full history — open, closed, and
reopened tickets. Add | ||||||||
| 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-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "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": "1757580000000",
- "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": "1757580000000",
- "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
Assigns between 1 and 50 tickets in one request. Each item names a ticket and the user to assign it to.
A ticket already assigned to that user is left unchanged and is still returned. Every ticket must be open and not spam, and the assignee must be a user on your store who can access that ticket's account. If any item fails those checks, nothing is assigned.
Send each ticket id once. The response lists the tickets in the same
order you sent them. Pass expand=assignee to replace
assigneeUserId with { id, name }.
| expand | string Example: expand=account,assignee Comma-separated related objects to include. Replaces the matching id
field ( |
| ticketId required | string The ticket to assign. |
| assigneeUserId required | string <uuid> The user to assign the ticket to. |
| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | Array of objects (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
Array
| |||||||||||||||||||||||||||||||||||||||||||||
[- {
- "ticketId": "<id>",
- "assigneeUserId": "<id>"
}
]{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "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": "1757580000000",
- "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 100) 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> |
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Value: "account" Example: expand=account Pass |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "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": "1757580000000",
- "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, and every id must identify
a label in your store. An unknown label id returns 400 and leaves the
conversation unchanged. Adding a label already present, or removing one
that is absent, is a no-op.
| addLabels required | Array of strings [ 1 .. 10 ] items |
| conversationId required | string |
| removeLabels | Array of strings <= 10 items |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "removeLabels": [
- "<id>"
], - "conversationId": "<id>",
- "addLabels": [
- "<id>"
]
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: contacts:read
Returns a single contact. Use GET /contacts/search to search by text,
or POST /contacts/list for a page of full contacts.
| 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": "1757580000000",
- "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": "1757580000000",
- "storeId": "<id>",
- "data": {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "firstName": "Somchai",
- "lastName": "Srisuk",
- "phone": "+66812345678",
- "createdAt": "<timestamp>",
- "updatedAt": "<timestamp>"
}
}Required scope: contacts:read
Searches contacts in your store. Each page contains 10 contacts, most
relevant first. Pass cursor from the previous x-pagination-next-cursor
header to continue. Do not send pageSize.
query is required, from 1 to 150 characters. Each hit includes the
fields that matched in matchedFields. This is not the full contact —
use GET /contacts with contactId for the complete record.
| query required | string [ 1 .. 150 ] characters Example: query=Somchai |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||
| storeId required | string | ||||||||||||||||||
required | Array of objects (ContactSearchHit) | ||||||||||||||||||
Array
| |||||||||||||||||||
curl "https://openplatform.zaapi.co/v1/contacts/search?query=Somchai" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "phone": "+66812345678",
- "matchedFields": [
- "fullName"
]
}
]
}Required scope: contacts:read
Returns contacts matching the optional search term and updated-at
window. Without search, results are ordered by most recently updated.
With search, results are ordered by relevance, then by most recently
updated. Whitespace-only search is treated as omitted. Paginate with
pageSize (max 100) and cursor.
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| 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-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "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": "1757580000000",
- "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 customFieldsUpdatedAt is absent, pass the contact's updatedAt
instead. If the contact changed in the meantime the write is rejected
with 409. Re-read the contact and retry with its latest timestamp.
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": "1757580000000",
- "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.
Each message includes id, conversationId, messageDirection, and
sentAt. text is the payload text, when there is any. images lists
image URLs, when the message has any. messageDirection
is inbound when the contact sent it and outbound when the store sent
it. messageCategory is message. type, when set, is how the message
was produced: auto_response, human_agent, one_time_noti,
seller_only_messages, or deferred. sender is channel sender
metadata, when present.
Paginate with pageSize (defaults to 15 here, max 100) and cursor.
Pass expand=sentBy,conversation,account to add related names.
conversationId stays on the message.
| conversationId | string Example: conversationId=<id> |
| ticketId | string Example: ticketId=<id> Limit results to this ticket's time window. |
| pageSize | integer [ 1 .. 100 ] Default: 15 Example: pageSize=15 Items per page. Defaults to 15 on this endpoint. Maximum 100. |
| 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-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "text": "Where is my order?",
- "messageDirection": "inbound",
- "messageCategory": "message",
- "sentAt": "<timestamp>"
}
]
}Required scope: messages:read
Searches message text. Each page contains 10 messages, most relevant
first. Pass cursor from the previous x-pagination-next-cursor
header to continue. Do not send pageSize.
query is required, from 1 to 150 characters. sentByRecipient is
true when the contact sent the message. score is the relevance
score for that hit.
| query required | string [ 1 .. 150 ] characters Example: query=cancel my order |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||
| requestReceivedAt required | string | ||||||||||||
| storeId required | string | ||||||||||||
required | Array of objects (MessageSearchHit) | ||||||||||||
Array
| |||||||||||||
curl "https://openplatform.zaapi.co/v1/messages/search?query=cancel%20my%20order" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "text": "cancel my order",
- "sentByRecipient": true,
- "sentAt": "<timestamp>",
- "score": 4
}
]
}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 100) and cursor. Pass
expand=createdBy,conversation,account to resolve related names.
| conversationId required | string Example: conversationId=<id> |
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| 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-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "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.
Tag a user by embedding <@userId:name@> in text. userId is
that user's userId from GET /users, and name is their display
name. Repeat the token to tag more than one person. Example:
Can you take this, <@3f2a9c1e-8b4d-4e71-9a06-2c8f1b7d4e90:John Smith@>?
| conversationId required | string |
| text required | string non-empty Comment body. Tag a user with |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "text": "Can you take this, <@3f2a9c1e-8b4d-4e71-9a06-2c8f1b7d4e90:John Smith@>?"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "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 [ 1 .. 1000 ] characters |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "text": "Thanks for your order!"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: messages:write
Sends an email from a Gmail or Outlook account and starts a
conversation. accountId is an id from GET /accounts whose channel
is gmail or outlook.
Send text, html, or both. At least one must be a non-empty string.
to is required and accepts 1–50 addresses. cc, bcc, and subject
are optional.
The response acknowledges the send and does not include the new conversation. A retry sends another email.
| accountId required | string Gmail or Outlook account to send from. Use an id from | ||||
required | Array of objects (EmailAddress) [ 1 .. 50 ] items Primary recipients. | ||||
Array ([ 1 .. 50 ] items)
| |||||
Array of objects (EmailAddress) Carbon-copy recipients. | |||||
Array
| |||||
Array of objects (EmailAddress) Blind carbon-copy recipients. | |||||
Array
| |||||
| subject | string Subject line. | ||||
| text | string Plain text body. | ||||
| html | string HTML body. | ||||
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "accountId": "<id>",
- "subject": "Your order is on the way",
- "text": "Your order is on the way."
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: messages:write
Sends an email on an existing Gmail or Outlook conversation. Send
text, html, or both. At least one must be a non-empty string.
to is required, because Outlook cannot infer recipients from the
thread.
The response acknowledges the send. A retry sends another email.
| conversationId required | string Gmail or Outlook conversation to send on. | ||||
required | Array of objects (EmailAddress) [ 1 .. 50 ] items Primary recipients. | ||||
Array ([ 1 .. 50 ] items)
| |||||
Array of objects (EmailAddress) Carbon-copy recipients. | |||||
Array
| |||||
Array of objects (EmailAddress) Blind carbon-copy recipients. | |||||
Array
| |||||
| subject | string Subject line. | ||||
| text | string Plain text body. | ||||
| html | string HTML body. | ||||
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "subject": "Re: Your order",
- "html": "<p>Your order is on the way.</p>"
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: messages:write
Sends an approved WhatsApp template on an existing WhatsApp
conversation. templateId comes from GET /accounts/whatsapp-templates.
When the template body contains {{1}}, {{2}}, and so on, pass
variables in that order. Omit variables when the body has no
placeholders.
The response acknowledges the send. A retry sends the template again.
| conversationId required | string WhatsApp conversation to send on. |
| templateId required | string Template id from |
| variables | Array of strings Positional body values for a template whose body contains |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "conversationId": "<id>",
- "templateId": "<id>",
- "variables": [
- "Nadia",
- "SO-10482"
]
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}Required scope: messages:write
Sends an approved WhatsApp template to a phone number from a WhatsApp
account. This starts a conversation when that phone has no thread on
the account. accountId is an id from GET /accounts whose channel
is whatsapp. phoneNumber is E.164, for example +66812345678.
templateId and variables work the same way as sending a template
on an existing conversation.
The response acknowledges the send and does not include the conversation. A retry sends the template again.
| accountId required | string WhatsApp account to send from. Use an id from |
| phoneNumber required | string Recipient phone number in E.164 format. |
| templateId required | string Template id from |
| variables | Array of strings Positional body values for a template whose body contains |
| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "accountId": "<id>",
- "phoneNumber": "+66812345678",
- "templateId": "<id>",
- "variables": [
- "Nadia",
- "SO-10482"
]
}{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "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. The display name may
also be absent on an incomplete invitation.
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": "1757580000000",
- "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 100) 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). |
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "label": "VIP",
- "color": "#09c8ab"
}
]
}Required scope: teams:read
Returns teams for the store, oldest first. Paginate with pageSize
(max 100) 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). |
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| expand | string Value: "users" Example: expand=users Pass |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| 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": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "Support",
- "description": "Customer support team",
- "users": [
- "<id>"
]
}
]
}Required scope: accounts:read
Returns a page of the channel accounts connected to your store. Only
active accounts are returned. Pass channel to keep only the accounts
on one channel. Use status to tell whether an account is currently
healthy.
Paginate with pageSize (max 100) and cursor. Accounts are returned
oldest first.
| channel | string (Channel) Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "drunken_lullabies" "whatsapp" … 3 more Return only the accounts on this channel. |
| pageSize | integer [ 1 .. 100 ] Default: 20 Example: pageSize=20 Items per page. Defaults to 20. Maximum 100. |
| cursor | string Example: cursor=<cursor> Opaque token from the previous page's |
| x-pagination-page-size | integer Example: "20" Page size used for this response. |
| x-pagination-next-cursor | string Example: "<cursor>" Pass as |
| requestId required | string | ||||||||||||
| requestReceivedAt required | string | ||||||||||||
| storeId required | string | ||||||||||||
required | Array of objects (Account) | ||||||||||||
Array
| |||||||||||||
curl "https://openplatform.zaapi.co/v1/accounts?channel=line&pageSize=20" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "channel": "line",
- "name": "Line Shop",
- "status": "healthy",
- "lastAuthorizedAt": "<timestamp>",
- "metadata": {
- "userName": "@mpe9992b",
- "country": "TH",
- "followerCount": 44117
}
}
]
}Required scope: accounts:read
Returns message templates for one WhatsApp account, up to 100.
accountId is an id from GET /accounts whose channel is
whatsapp.
Filter with search (template name or body), language, status, or
category. This list is not paginated. Narrow the filters when the
account has more templates than the response includes.
Use id from a template as templateId when sending it. When a BODY
component contains {{1}}, {{2}}, and so on, pass those values as
variables, in that order.
| accountId required | string Example: accountId=<id> WhatsApp account to list templates for. |
| search | string Example: search=order Match template name or body text. |
| language | string Example: language=en Template language code. |
| status | string (WhatsappTemplateStatus) Enum: "APPROVED" "ARCHIVED" "DELETED" "DISABLED" "FLAGGED" "IN_APPEAL" "LIMIT_EXCEEDED" "LOCKED" … 5 more Template review status. |
| category | string (WhatsappTemplateCategory) Enum: "MARKETING" "UTILITY" Template category. |
| requestId required | string | ||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||
| storeId required | string | ||||||||||||||||
required | Array of objects (WhatsappTemplate) | ||||||||||||||||
Array
| |||||||||||||||||
curl "https://openplatform.zaapi.co/v1/accounts/whatsapp-templates?accountId=<id>&status=APPROVED" \ -H "x-api-key: YOUR_API_KEY"
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "order_update",
- "parameter_format": "POSITIONAL",
- "language": "en",
- "status": "APPROVED",
- "category": "UTILITY",
- "components": [
- {
- "type": "BODY",
- "text": "Hi {{1}}, your order {{2}} is on the way."
}
]
}
]
}| requestId required | string Gateway request id. Include this when you contact support. | ||||
| requestReceivedAt required | string Unix time in milliseconds, as a string ( | ||||
| storeId required | string | ||||
required | object | ||||
| |||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "error": {
- "message": "Resource not found",
- "statusCode": 404
}
}| requestId required | string | ||
| requestReceivedAt required | string | ||
| storeId required | string | ||
required | object | ||
| |||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": {
- "message": "OK"
}
}| requestId required | string | ||||||||||||||||||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||||||||||||||||||
required | object (Ticket) | ||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "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": "1757580000000",
- "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": "1757580000000",
- "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. | ||||||
| isOpen required | boolean | ||||||
| openedAt required | string <date-time> | ||||||
| accountId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| conversationId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| channel | string (Channel) Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "drunken_lullabies" "whatsapp" … 3 more | ||||||
| closedAt | string <date-time> | ||||||
| reopenedAt | string <date-time> | ||||||
| assigneeUserId | string Present unless | ||||||
object Replaces | |||||||
| |||||||
| isFollowUp | boolean | ||||||
| isSpam | 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. | ||||||
| contactId | string | ||||||
| createdAt | string <date-time> | ||||||
| updatedAt | string <date-time> | ||||||
{- "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 (Channel) Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "drunken_lullabies" "whatsapp" … 3 more |
{- "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 (Channel) Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "drunken_lullabies" "whatsapp" … 3 more |
{- "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. Sent on its
own, returns the conversation's full history — open, closed, and
reopened tickets. Add | ||||||||
| 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) | |||||||||||
Array
| |||||||||||
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 .. 20 ] 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 The ticket to assign. |
| assigneeUserId required | string <uuid> The user to assign the ticket to. |
[- {
- "ticketId": "<id>",
- "assigneeUserId": "<id>"
}
]| ticketId required | string The ticket to assign. |
| assigneeUserId required | string <uuid> The user to assign the ticket to. |
{- "ticketId": "string",
- "assigneeUserId": "11d6662f-b88d-444d-8712-eda11b5a0eed"
}| 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": "1757580000000",
- "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" "drunken_lullabies" "whatsapp" … 3 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>"
}| addLabels required | Array of strings [ 1 .. 10 ] items |
| conversationId required | string |
| removeLabels | Array of strings <= 10 items |
{- "removeLabels": [
- "<id>"
], - "conversationId": "<id>",
- "addLabels": [
- "<id>"
]
}| requestId required | string | ||||||||||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||||||||||
required | object (Contact) | ||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "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": "1757580000000",
- "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 (ContactSearchHit) | ||||||||||||||||||
Array
| |||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "phone": "+66812345678",
- "matchedFields": [
- "fullName"
]
}
]
}| id required | string |
| fullName required | string |
| matchedFields required | Array of strings Fields that matched the query. |
| phone | string |
string <email> | |
| secondaryPhones | Array of strings |
| secondaryEmails | Array of strings <email> [ items <email > ] |
| addresses | Array of strings |
| note | string |
{- "id": "<id>",
- "fullName": "Somchai Srisuk",
- "phone": "+66812345678",
- "matchedFields": [
- "fullName"
]
}| requestId required | string | ||||||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||||||
| storeId required | string | ||||||||||||||||||||
required | Array of objects (ContactField) | ||||||||||||||||||||
Array
| |||||||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "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": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "text": "Where is my order?",
- "messageDirection": "inbound",
- "messageCategory": "message",
- "sentAt": "<timestamp>"
}
]
}| id required | string | ||||||
| conversationId required | string | ||||||
| messageDirection required | string Enum: "inbound" "outbound"
| ||||||
| messageCategory required | string Value: "message" Customer-facing messages are | ||||||
| sentAt required | string <date-time> | ||||||
| text | string Payload text, when the message has any. | ||||||
| images | Array of strings <uri> [ items <uri > ] Image URLs, when the message has any. | ||||||
| type | string Enum: "auto_response" "human_agent" "one_time_noti" "seller_only_messages" "deferred" How the message was produced, when set. | ||||||
object Channel sender metadata, when present. | |||||||
| |||||||
object Present when | |||||||
| |||||||
object Present when | |||||||
| |||||||
object Present when | |||||||
| |||||||
{- "id": "<id>",
- "conversationId": "<id>",
- "text": "Where is my order?",
- "messageDirection": "inbound",
- "messageCategory": "message",
- "sentAt": "<timestamp>"
}| requestId required | string | ||||||||||||
| requestReceivedAt required | string | ||||||||||||
| storeId required | string | ||||||||||||
required | Array of objects (MessageSearchHit) | ||||||||||||
Array
| |||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "conversationId": "<id>",
- "text": "cancel my order",
- "sentByRecipient": true,
- "sentAt": "<timestamp>",
- "score": 4
}
]
}| id required | string |
| conversationId required | string |
| text required | string |
| sentByRecipient required | boolean
|
| sentAt required | string <date-time> |
| score required | number Relevance score for this hit. |
{- "id": "<id>",
- "conversationId": "<id>",
- "text": "cancel my order",
- "sentByRecipient": true,
- "sentAt": "<timestamp>",
- "score": 4
}| 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": "1757580000000",
- "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 [ 1 .. 1000 ] characters |
{- "conversationId": "<id>",
- "text": "Thanks for your order!"
}| conversationId required | string |
| text required | string non-empty Comment body. Tag a user with |
{- "conversationId": "<id>",
- "text": "Can you take this, <@3f2a9c1e-8b4d-4e71-9a06-2c8f1b7d4e90:John Smith@>?"
}| email required | string <email> |
| name | string Display name shown beside the address. |
{- "name": "Nadia Prasert"
}| accountId required | string Gmail or Outlook account to send from. Use an id from | ||||
required | Array of objects (EmailAddress) [ 1 .. 50 ] items Primary recipients. | ||||
Array ([ 1 .. 50 ] items)
| |||||
Array of objects (EmailAddress) Carbon-copy recipients. | |||||
Array
| |||||
Array of objects (EmailAddress) Blind carbon-copy recipients. | |||||
Array
| |||||
| subject | string Subject line. | ||||
| text | string Plain text body. | ||||
| html | string HTML body. | ||||
{- "accountId": "<id>",
- "subject": "Your order is on the way",
- "text": "Your order is on the way."
}| conversationId required | string Gmail or Outlook conversation to send on. | ||||
required | Array of objects (EmailAddress) [ 1 .. 50 ] items Primary recipients. | ||||
Array ([ 1 .. 50 ] items)
| |||||
Array of objects (EmailAddress) Carbon-copy recipients. | |||||
Array
| |||||
Array of objects (EmailAddress) Blind carbon-copy recipients. | |||||
Array
| |||||
| subject | string Subject line. | ||||
| text | string Plain text body. | ||||
| html | string HTML body. | ||||
{- "conversationId": "<id>",
- "subject": "Re: Your order",
- "html": "<p>Your order is on the way.</p>"
}| conversationId required | string WhatsApp conversation to send on. |
| templateId required | string Template id from |
| variables | Array of strings Positional body values for a template whose body contains |
{- "conversationId": "<id>",
- "templateId": "<id>",
- "variables": [
- "Nadia",
- "SO-10482"
]
}| accountId required | string WhatsApp account to send from. Use an id from |
| phoneNumber required | string Recipient phone number in E.164 format. |
| templateId required | string Template id from |
| variables | Array of strings Positional body values for a template whose body contains |
{- "accountId": "<id>",
- "phoneNumber": "+66812345678",
- "templateId": "<id>",
- "variables": [
- "Nadia",
- "SO-10482"
]
}| requestId required | string | ||||||||||
| requestReceivedAt required | string | ||||||||||
| storeId required | string | ||||||||||
required | Array of objects (User) | ||||||||||
Array
| |||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "userId": "<id>",
- "name": "Nadia Prasert",
- "roleId": "<id>"
}
]
}| id required | string | ||||
| name | string Absent on incomplete invitations. | ||||
| userId | string <uuid> 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": "1757580000000",
- "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": "1757580000000",
- "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>"
]
}| requestId required | string | ||||||||||||
| requestReceivedAt required | string | ||||||||||||
| storeId required | string | ||||||||||||
required | Array of objects (Account) | ||||||||||||
Array
| |||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "channel": "line",
- "name": "Line Shop",
- "status": "healthy",
- "lastAuthorizedAt": "<timestamp>",
- "metadata": {
- "userName": "@mpe9992b",
- "country": "TH",
- "followerCount": 44117
}
}
]
}| id required | string | ||||||||
| channel required | string (Channel) Enum: "line" "instagram" "facebook" "lazada" "shopee" "tiktok" "drunken_lullabies" "whatsapp" … 3 more | ||||||||
| name | string Display name of the account as shown on the channel. | ||||||||
| status | string Enum: "healthy" "unhealthy" "disconnected" Absent on accounts that have never reported a status. | ||||||||
| lastAuthorizedAt | string <date-time> When the account was last authorised against the channel. | ||||||||
object Channel metadata. Which fields are present depends on the channel. | |||||||||
| |||||||||
{- "id": "<id>",
- "channel": "line",
- "name": "Line Shop",
- "status": "healthy",
- "lastAuthorizedAt": "<timestamp>",
- "metadata": {
- "userName": "@mpe9992b",
- "country": "TH",
- "followerCount": 44117
}
}| requestId required | string | ||||||||||||||||
| requestReceivedAt required | string | ||||||||||||||||
| storeId required | string | ||||||||||||||||
required | Array of objects (WhatsappTemplate) | ||||||||||||||||
Array
| |||||||||||||||||
{- "requestId": "<requestId>",
- "requestReceivedAt": "1757580000000",
- "storeId": "<id>",
- "data": [
- {
- "id": "<id>",
- "name": "order_update",
- "parameter_format": "POSITIONAL",
- "language": "en",
- "status": "APPROVED",
- "category": "UTILITY",
- "components": [
- {
- "type": "BODY",
- "text": "Hi {{1}}, your order {{2}} is on the way."
}
]
}
]
}"APPROVED"| id required | string Template id. Pass this as | ||||||||
| name required | string | ||||||||
| parameter_format required | string How body placeholders are filled. | ||||||||
| language required | string Template language code. | ||||||||
| status required | string (WhatsappTemplateStatus) Enum: "APPROVED" "ARCHIVED" "DELETED" "DISABLED" "FLAGGED" "IN_APPEAL" "LIMIT_EXCEEDED" "LOCKED" … 5 more | ||||||||
| category required | string (WhatsappTemplateCategory) Enum: "MARKETING" "UTILITY" | ||||||||
required | Array of objects (WhatsappTemplateComponent) Template parts. A | ||||||||
Array
| |||||||||
object (WhatsappTemplateQualityScore) | |||||||||
| |||||||||
{- "id": "<id>",
- "name": "order_update",
- "parameter_format": "POSITIONAL",
- "language": "en",
- "status": "APPROVED",
- "category": "UTILITY",
- "components": [
- {
- "type": "BODY",
- "text": "Hi {{1}}, your order {{2}} is on the way."
}
]
}| type required | string Enum: "GREETING" "HEADER" "BODY" "FOOTER" "BUTTONS" "CAROUSEL" "LIMITED_TIME_OFFER" "CALL_PERMISSION_REQUEST" | ||||||||
| format | string Present on some headers, for example | ||||||||
| text | string Component text. Body text may contain | ||||||||
Array of objects (WhatsappTemplateButton) | |||||||||
Array
| |||||||||
{- "type": "GREETING",
- "format": "string",
- "text": "string",
- "buttons": [
- {
- "type": "PHONE_NUMBER",
- "text": "string",
- "url": "string",
- "phone_number": "string"
}
]
}| type required | string Enum: "PHONE_NUMBER" "URL" "QUICK_REPLY" "COPY_CODE" |
| text required | string |
| url | string Present on |
| phone_number | string Present on |
{- "type": "PHONE_NUMBER",
- "text": "string",
- "url": "string",
- "phone_number": "string"
}