Conversation and management endpoints
App session endpoints for conversations, visitor blocks, team, routing, catalogue, shop, analytics, cases and audit log: fields, limits, roles and errors.
In plain language
The app uses these endpoints to read and change its data. They cover chats and blocked visitors. The team and the routing rules belong here too. Other endpoints deal with the catalogue, the shop and analytics. Cases and the activity log are also covered. You need the token from the sign-in. Your own systems with a key use other endpoints.
All documentation topics
This page covers the endpoints the app itself uses to read and change conversations, visitor blocks, the team, routing rules, catalogue and shop connection, analytics, cases and the audit log. All of them live under https://app.xicbot.de/api/v1/ and expect a session token in the Authorization: Bearer … header, as described in Authentication. Connected systems using an API key have their own endpoints, see Integration.
Access, roles and throttles
Every request passes three stages. First the server checks the session token and whether the account is active. Next, the signed-in person’s email address has to be confirmed; until it is, every endpoint on this page answers 403 with email_unverified, and the app shows “This area stays locked until the email address is confirmed.” Last, each endpoint checks its minimum role, and some also check a plan feature and a named throttle.
Roles
Roles are ranked: a higher role may do whatever a lower one may. How to assign roles is explained in Team and roles.
| Role in the API | Label in the app | Endpoints on this page |
|---|---|---|
viewer | Read | reading endpoints except the audit log, own availability, accepting an invitation |
editor | Edit | working on, creating and writing to conversations, routing rules, catalogue, case status |
admin | Administration | deleting conversations, retention periods, visitor blocks, team, shop connection, audit log |
owner | Owner | plain text of a conversation, plain-text export, weekly report subscription |
Common rejections
Almost any endpoint can return these. The body is JSON with an error field; the general format and 422 validation errors are described in API basics.
| Status | error | Meaning |
|---|---|---|
401 | – | Session token missing or no longer valid, see Authentication. |
403 | no_tenant | The person belongs to no account. |
403 | tenant_<Status> | The account is not active. message reads “Das Konto ist derzeit nicht aktiv.” (German) |
403 | email_unverified | The email address is not confirmed. |
403 | role_too_low | The role is not sufficient; required names the role needed. |
402 | plan_feature_missing | The plan lacks the feature; with feature, plan, plan_name and available_in. |
402 | plan_limit_reached | A plan limit has been reached; with limit, allowed, used, plan and plan_name. |
503 | plan_limit_unmeasured | The counter for a plan limit could not be read; with limit and reason. The endpoint then does not let the request through. |
429 | – | A named throttle was exceeded, see Limits. |
500 | not_logged | The audit log entry could not be written; the change is not saved. The app shows “Not saved: the entry in the audit log could not be written. Please try again.” |
Which features and limits your plan includes is listed in Plans and quotas and on the pricing page; how the plan gate answers is covered in Limits.
Throttles
Some endpoints carry a named throttle. It counts per signed-in person, or per request origin if there is none, in a one-minute window.
| Throttle | Requests per minute | Endpoints |
|---|---|---|
conversations | 30 | CSV export, creating a conversation, rating, plain text, attachments, analytics report |
conversation-send | 20 | Sending a message into a conversation |
catalog | 20 | Creating, changing and importing products; saving, testing and syncing the shop connection |
invitations | 5 | Sending an invitation |
Conversations
In the app these endpoints sit behind Conversations and Inbox; what you see there is described in Conversations and Inbox. Any role may read; every action on a conversation needs at least Edit.
List
GET /api/v1/conversations
Returns one page of conversations, masked, filtered and sorted.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Parameter | Values | Default | Effect |
|---|---|---|---|
page | 1 to 10000 | 1 | Page |
per_page | 1 to 200 | 50 | Entries per page |
sort | created_at, message_count, frustration_score, status, escalated_at | created_at | Sort column; ties are broken by the ID |
dir | asc, desc | desc | Direction |
assistant | ID, up to 64 characters | – | only this assistant’s conversations |
channel | test, widget, api | – | Where the conversation came from |
status | open, closed, escalated | – | State |
language | unknown or a language code such as de or de-at | – | unknown finds conversations whose language was not detected |
mailbox | 0, 1 | – | 1: handed over and not yet resolved, the list in the Inbox |
assigned | none, me or a person’s ID, up to 64 characters | – | Assignment; none finds conversations nobody is assigned to |
queue | Name of a queue, up to 80 characters, or none | – | Queue set by routing; none finds conversations without a queue |
unverified | 0, 1 | – | 1: conversations with at least one reply whose claim is marked unverified |
q | Search term, up to 120 characters | – | Search in title and message text |
from, to | Date or timestamp | – | Range by creation time; a value without offset applies in zone; a plain date in to includes that whole day |
zone | Time zone name, up to 64 characters | Europe/Berlin | Zone in which from and to are read; day boundaries and the day after a plain date are calculated there. Unknown and with from or to: 422 with unknown_timezone |
A value of the wrong type is rejected rather than ignored, with 422. The search matches substrings; % and _ are taken literally. With the Owner role the search runs on plain text, with any other role on the masked version, so you only find what your role can see. If the search on the masked version finds more than 20000 conversations, the endpoint answers 422 with search_too_broad and limit instead of shortening the list; narrow the date range or filters. Wherever the filter is logged, the search appears only as "q": true, not the term itself.
| Field | Content |
|---|---|
conversations | Conversations on this page, fields see Conversation fields |
total | Number of all matches |
page, per_page | Page and page size |
sort, dir | Sorting applied |
language | Language filter applied |
languages | Up to 50 detected conversation languages, alphabetical |
masked | true: the list is masked |
Conversation fields
| Field | Content |
|---|---|
id | ID |
assistant_id, assistant | Assistant ID and name |
channel | widget, test or api |
host | Origin for the widget; for test and API conversations the host name from the request’s Origin or Referer, null if neither is present |
language | Detected language |
title | Title, masked except in plain text |
status | open, closed or escalated |
message_count | Number of messages |
created, updated | ISO 8601 timestamps |
frustration_score | Frustration score of the conversation |
escalation_reason, escalated_at | Handover reason, masked, and time of handover |
assigned_user_id, assigned_to, assigned_at | Assigned person; assigned_to is their name, or their email address if there is none |
taken_over_at | Start of an active takeover, otherwise null |
queue | Queue set by routing |
resolved_at | Time it was resolved |
flagged_at | Time it was flagged, otherwise null |
tags | Tags, [] if none |
internal_note | Internal note |
masked | true unless the conversation is read in plain text |
Single view, plain text and attachments
GET /api/v1/conversations/{id}
Returns one conversation with its history, masked. An unknown ID gives 404 with not_found.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Field | Content |
|---|---|
conversation | Fields as in Conversation fields |
messages | History made of the latest 500 messages and the latest 500 tool calls, older entries are dropped at the start; texts masked; assistant replies carry rating (1, 0 or null) |
masked | true in this view |
masked_kinds | Kinds of personal data masked in the history |
access_log | The last 20 plain-text accesses to this conversation (conversation.revealed) with id, time, action, who and reason |
attachments | Attachments with id, name, mime, bytes, sha256, lead_id and time |
In tool calls in the history the structure fields stay unmasked: id, key, kind, state, duration_ms, approval_path, needs_confirmation, chain_capped and created_at; the other fields go through masking. The values of unverified claims are masked as well. The app states: “Personal data is masked: email, phone, IBAN and postal address.”
POST /api/v1/conversations/{id}/reveal
Returns the same conversation in plain text, with masked: false. Every access is logged as conversation.revealed with its reason and then shows up in access_log.
- Authentication
- Session token, Owner only
- Rate limit
conversations, 30 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
reason | yes | 5 to 200 characters | Reason for the access; in the app Reason for this access |
| Status | error | Meaning |
|---|---|---|
403 | role_too_low | Role is not Owner; required is owner. |
422 | – | Reason missing or too short; the app says “Please give a reason of at least five characters.” |
404 | not_found | Unknown conversation. |
500 | not_logged | No audit log entry, so no plain text. |
GET /api/v1/attachments/{id}
Downloads an attachment as a file. Every download is logged as attachment.read.
- Authentication
- Session token, any role
- Rate limit
conversations, 30 per minute
| Response or header | Content |
|---|---|
200 | File with Content-Disposition: attachment; a type that is not on the allow list is sent as application/octet-stream |
Content-Security-Policy | default-src 'none'; sandbox |
X-Content-Type-Options | nosniff |
Cache-Control | private, no-store, max-age=0 |
Referrer-Policy | no-referrer |
404 not_found | Unknown attachment |
410 file_missing | The record exists but the file is missing; with measured: false |
CSV export
Both exports require the plan feature for conversation export and carry the conversations throttle. If your plan lacks the feature, they answer 402 plan_feature_missing, with feature set to conversation_export. Without reveal=1 the content is masked. With reveal=1 you need the Owner role and a reason. Every export is logged as conversation.exported, with the plain-text flag, reason, row count and filter; if no entry is created, only the plain-text export stops with 500 and not_logged; the masked export carries on without an entry.
GET /api/v1/conversations/export
Exports the filtered conversations as CSV, one row per conversation with the columns id, created_at, assistant, channel, language, status, messages, title and escalation. The list filters apply. In the app this is Filtered conversations as CSV.
- Authentication
- Session token, any role; plain text Owner only
- Rate limit
conversations, 30 per minute
| Parameter | Values | Meaning |
|---|---|---|
| List filters | as above | narrow the export |
reveal | 0, 1 | 1 requests plain text |
reason | up to 200 characters, at least 5 with reveal=1 | Reason for the plain-text export |
| Response | Content |
|---|---|
content-type | text/csv; charset=utf-8 |
content-disposition | Download named conversations.csv |
x-rows | Number of rows |
x-masked | 1 masked, 0 plain text |
| Body | Columns separated by semicolons, CRLF line endings |
422 too_many | More than 5000 matches; with total and limit. The app says “Too many conversations for one export. Please narrow the date range.” |
422 reason_required | reveal=1 without a reason of at least 5 characters; with min |
403 role_too_low | reveal=1 without the Owner role |
402 plan_feature_missing | Your plan does not include conversation export; with feature, plan, plan_name and available_in |
GET /api/v1/conversations/{id}/export
Exports the history of one conversation as CSV, one row per message or tool call, with the columns time, role, text, state, rating, tool_key and tool_state. The file name is gespraech-<id>.csv.
- Authentication
- Session token, any role; plain text Owner only
- Rate limit
conversations, 30 per minute
| Response | Content |
|---|---|
x-rows | Number of rows |
x-capped | 1 if the history was cut off at 500 messages or 500 tool calls, otherwise 0 |
404 not_found | Unknown conversation |
| Others | 402 plan_feature_missing, reveal, reason and their errors as for the list export |
Working on conversations
Every endpoint in this section needs at least Edit. An unknown conversation ID gives 404 with not_found. Unless stated otherwise, they answer with the conversation in the conversation fields.
POST /api/v1/conversations/{id}/escalate
Hands a conversation over to the team manually: status becomes escalated, escalated_at is set and an earlier resolution is cleared. The reason is appended to any existing handover reason, 300 characters in total; without a reason the text is “Von Hand übergeben” (German). This handover also runs through the routing rules. If the conversation is already handed over, it stays unchanged, including its reason, and the rules do not run again. Either way the response contains conversation and unchanged (true if nothing was changed).
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
reason | no | up to 200 characters | Handover reason |
POST /api/v1/conversations/{id}/assign
Sets the assigned person. user_id: null or an empty string removes the assignment. An ID that does not belong to the account gives 422 with unknown_user. In the app the buttons are Take on and Release.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
user_id | no | ID, up to 64 characters, or null | Assigned person |
POST /api/v1/conversations/{id}/takeover
Takes over a widget conversation and makes you the assigned person; an existing assigned_at stays. While the takeover lasts, the assistant does not answer; your messages reach the visitor as team messages. The app calls this Take over conversation and then shows “You are handling this conversation. The assistant does not answer.” Calling it again leaves the timestamps unchanged. 409 only comes while someone else in the account holds the takeover; an assignment without a takeover passes to you. A takeover by a person who no longer belongs to the account counts as free and passes to you with a new time.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
| Status | error | Meaning |
|---|---|---|
422 | not_live | The conversation did not come from the widget. |
403 | consent_revoked | The visitor withdrew consent, see Consent and visitor rights. |
409 | taken_over_by_other | Someone else holds the takeover; assigned_to names them. |
POST /api/v1/conversations/{id}/release
Ends the takeover; taken_over_at becomes null and the assignment stays. In the app this is End takeover. Releasing a conversation that is not taken over is not an error. If someone else in the account holds the takeover, the endpoint answers 409 with taken_over_by_other; a takeover by a person who no longer belongs to the account can be ended.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
POST /api/v1/conversations/{id}/resolve
Marks the conversation as resolved: status becomes closed, resolved_at is set, your own takeover ends (taken_over_at becomes null) and the conversation.ended webhook fires. A conversation that is already closed stays unchanged, including its first resolved_at, and fires no second event; the response then carries unchanged: true. If another person in the account is handling the conversation, you get 409 with taken_over_by_other and assigned_to. In the app this is Mark as done.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
POST /api/v1/conversations/{id}/mark
Sets the flag, tags and internal note. Only what is in the body changes. In the app the fields are Flag this conversation, Tags and Internal note.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
flagged | no | true or false | true sets flagged_at, an existing flag keeps its time; false clears it |
tags | no | List of up to 10 entries, each up to 40 characters | Surrounding spaces and duplicates are removed; an empty list removes all tags |
note | no | up to 2000 characters | Internal note; empty removes it |
POST /api/v1/conversations/{id}/messages/{message}/rating
Rates an assistant reply: 1 Helpful, 0 Not helpful, null removes the rating (Not rated yet). The response contains message with id and rating.
- Authentication
- Session token, Edit or higher
- Rate limit
conversations, 30 per minute
| Status | error | Meaning |
|---|---|---|
422 | – | rating is missing from the body or is not 0, 1 or null. |
404 | not_found | The message does not belong to this conversation. |
422 | not_rateable | The message was not written by the assistant. |
POST /api/v1/conversations/{id}/tool/{call}/confirm
Confirms a proposed tool call that is waiting for approval. The response contains call and duplicate. If the call is no longer in the proposed state, or another click got there first, you get the same state back with duplicate: true instead of an error. An unknown call gives 404 with not_found. Tools are covered in Tools.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
POST /api/v1/conversations/{id}/tool/{call}/reject
Rejects a proposed tool call. Response and behaviour on a second click match the confirm endpoint; an unknown call gives 404 with not_found.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
Create, write and delete
POST /api/v1/conversations
Creates a test conversation with channel test and status open. The 201 response contains conversation and greeting, the greeting of the active version. What test conversations are for is explained in Test and acceptance.
- Authentication
- Session token, Edit or higher
- Rate limit
conversations, 30 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
assistant_id | yes | ID, up to 64 characters | Assistant for the conversation |
| Status | error | Meaning |
|---|---|---|
404 | not_found | Unknown assistant. |
422 | no_version | The assistant has no active version. |
POST /api/v1/conversations/{id}/send
Writes a message into a conversation. During a takeover the text is stored as a team message (role operator) and the response is 201 with message (id, role, text, author, state, time). Otherwise the assistant answers, and the reply arrives as an event stream as described in Integration.
- Authentication
- Session token, Edit or higher
- Rate limit
conversation-send, 20 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
text | yes | 1 to 4000 characters | Message |
| Status | error | Meaning |
|---|---|---|
409 | taken_over_by_other | Someone else holds the takeover; assigned_to names them. |
409 | not_taken_over | Widget conversation without a takeover: only whoever took over may write into a live visitor conversation. |
422 | no_version | The conversation’s assistant or version no longer exists. |
409 | reply_in_progress | A reply is already running for this conversation. |
DELETE /api/v1/conversations/{id}
Deletes a conversation and logs it as conversation.deleted. The response is {"deleted": true}. Without an audit log entry the conversation stays; you then get 500 with not_logged. Retention and deletion are covered in Retention and deletion.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
Retention periods
GET /api/v1/conversations/retention
Returns the periods in days: retention with general, test and api, plus default_general, the starting value for new accounts (90).
- Authentication
- Session token, any role
- Rate limit
- no named throttle
PUT /api/v1/conversations/retention
Sets the three periods and answers in the same shape as the read endpoint. The change is logged as retention.updated. In the app the fields are All conversations, Test conversations and Via the API; it also states “Retention periods can be changed by users with the Administration or Owner role.”
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
general | yes, may be null | 1 to 3650 | Period for all conversations; null means no period set |
test | yes, may be null | 1 to 3650 | Test conversations; null follows the general period |
api | yes, may be null | 1 to 3650 | Conversations via the API; null follows the general period |
Visitor blocks
A block turns away a visitor’s future widget conversations, either by their address (address) or by their session (session). It starts from a conversation; the block stores a hash of that conversation’s address or session. How to block from within the app is described in Conversations.
GET /api/v1/visitor-blocks
Returns the latest 200 blocks in data, newest first.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
POST /api/v1/visitor-blocks
Creates a block and logs it as visitor.blocked. If a block already exists for the same kind and hash, it is updated: reason and duration are overwritten, the hit counter is kept. The response is 201 with the block in data. Without an audit log entry the block is neither created nor changed; you then get 500 with not_logged.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
conversation_id | yes | UUID | Conversation to block from |
kind | yes | address, session | Kind of block |
reason | yes | 8 to 200 characters | Reason |
days | no | 1 to 3650 | Duration in days; open-ended if omitted |
| Status | error | Meaning |
|---|---|---|
404 | not_found | Unknown conversation. |
422 | no_address | No value for the chosen kind is stored for the conversation. |
500 | not_logged | No audit log entry, no block. |
DELETE /api/v1/visitor-blocks/{id}
Lifts a block, logged as visitor.unblocked. The response is {"ok": true}; an unknown block gives 404 with not_found.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
| Field | Content |
|---|---|
id, kind, reason | ID, kind and reason |
expires_at | End of the block or null for open-ended |
active | Whether the block currently applies |
hits, last_hit_at | Rejected attempts and the most recent one |
created_at, created_by | Creation time and name of the person who blocked |
conversation_id, host | Source conversation and its origin |
Team
In the app these endpoints sit behind Team. Roles and invitations from the interface’s point of view are described in Team and roles.
GET /api/v1/team
Returns members, open invitations and the seat count. At most 200 people are returned, sorted by email address; if the list is cut off, the app shows “The first {n} people are shown.”
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Field | Content |
|---|---|
members | People with id, name, email, role, available and available_at |
total, capped | Total number of people and whether the list is cut off |
invitations | Open invitations with id, email, role, expires_at and created_at |
seats | Seats with limit, used, measured, reason and exceeded; if the counter cannot be read, you get measured: false with reason: no_counter |
POST /api/v1/availability
Switches your own availability. available_at is set when switching on, if it is still empty, and cleared when switching off. The response contains member. In the app this is Mark me available and Mark me unavailable, with the note “Anyone marked available is shown in the inbox as available for a handover.” Until your email address is confirmed, the app shows “You can mark yourself as available once your email address is confirmed.” instead of the button.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
available | yes | true or false | Available or not |
POST /api/v1/team/invitations
Invites a person. The invitation is valid for 168 hours, that is seven days, and is sent by email with a link to the Accept invitation page, in the inviting person’s language. If sending fails, the invitation is still created. Logged as team.invited; the response is 201 with invitation. In the app: Invite someone, button Send invitation.
- Authentication
- Session token, Administration or higher
- Rate limit
invitations, 5 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
email | yes | valid address, up to 190 characters | Address of the invitee |
role | yes | viewer, editor, admin | Role after accepting; Owner cannot be assigned this way |
| Status | error | Meaning |
|---|---|---|
422 | validation | fields.email contains validation.already_member (already in the team) or validation.invitation_open (an invitation is still open). |
403 | role_too_low | Your own role is below the one being assigned. |
402 | plan_limit_reached | All seats are taken. The app says “All seats are taken. Withdraw an invitation or move to a larger plan.” |
DELETE /api/v1/team/invitations/{id}
Withdraws an open invitation; it is marked as withdrawn, not deleted. Logged as team.invitation_revoked, response {"revoked": true}. Unknown or no longer open invitations give 404 with not_found. In the app: Withdraw.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
PATCH /api/v1/team/members/{id}
Changes a person’s role to viewer, editor or admin. Logged as team.role_changed; the response contains member.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
| Status | error | Meaning |
|---|---|---|
404 | not_found | The person does not belong to the account. |
422 | not_self | Your own role; the app says “You cannot change your own role here.” |
422 | owner_protected | The owner; the app says “The owner cannot be changed or removed.” |
403 | role_too_low | Your own role is not sufficient for this change. |
DELETE /api/v1/team/members/{id}
Removes a person from the team and logs it as team.member_removed. If they are currently working in this account, their session tokens are deleted; if they then belong to no other account, their login is deleted, otherwise they switch to the oldest remaining account that has not been deleted. If they are currently working in another account, their sessions there continue. Response {"removed": true}; not_self and owner_protected as for role changes. In the app: Remove, with the prompt “Remove this person from the team? Open sessions end immediately.”
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
POST /api/v1/team/invitations/accept
Accepts an invitation as the signed-in person. Logged as team.invitation_accepted; the response contains joined: true, tenant with id and name, and role. The app then shows “You are now part of the team.”
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
token | yes | up to 120 characters | Token from the invitation link |
| Status | error | Meaning |
|---|---|---|
422 | invalid_token | Token unknown, expired or withdrawn; the app says “This invitation has expired or was withdrawn. Please ask for a new one.” |
403 | wrong_account | The signed-in email address is not the invited one. Both addresses are compared in their normalised form, for example without a suffix after a plus sign. |
Routing rules
On every handover, manual ones included, the server checks the active rules in order; the first match wins. It compares against the handover reason and the conversation title, lower-cased and without word boundaries: the keyword bill also matches “billing”. The rule sets the queue (queue) and, if nobody is assigned yet, its person; if that person no longer belongs to the account, only the queue is set. A conversation that already has a queue keeps it. If no rule matches, the queue stays empty.
In the app the area is called Routing, with the fields Queue and Keywords (several separated by commas). Without rules it shows “No rule yet. Without a rule every handover stays without a queue.” How queues appear in the Inbox is described there.
GET /api/v1/routing-rules
Returns rules in their order and max, the maximum of 50 rules.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
POST /api/v1/routing-rules
Adds a rule at the end of the list; without is_active it is active. The response is 201 with rule and the whole list in rules.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
queue | yes | up to 80 characters | Queue name |
keywords | yes | 1 to 20 entries, each up to 60 characters | Keywords |
assigned_user_id | no | ID, up to 64 characters | Person to assign |
is_active | no | true or false | Whether the rule is checked |
| Status | error | Meaning |
|---|---|---|
422 | unknown_user | The person does not belong to the account. |
422 | too_many_rules | There are already 50 rules. |
PUT /api/v1/routing-rules/{id}
Changes queue, keywords, person and active switch of a rule, using the same fields as for creating. If is_active is omitted, the rule becomes active again. Its position does not change. The response contains rule and the list in rules. An unknown rule gives 404 with not_found, here as well as when moving or deleting.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
POST /api/v1/routing-rules/{id}/up
Moves a rule up by one place. The server assigns positions. The response contains moved, false for the top rule, and the list in rules.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
DELETE /api/v1/routing-rules/{id}
Deletes a rule. The response contains deleted: true and the remaining list in rules.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
| Field | Content |
|---|---|
id, position | ID and place in the order |
queue, keywords | Queue and keywords |
assigned_user_id, assigned_to | Person and their name |
is_active | Active or not |
Catalogue and shop connection
Every endpoint in this section requires the plan feature for the shop connection; without it they answer 402 with plan_feature_missing. In the app this is under Catalogue, described in Catalogue and shop.
Products
GET /api/v1/catalog
Returns one page of products sorted by title in products, plus total, page and per_page. total counts every product in the account, even when search is set.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Parameter | Values | Default | Effect |
|---|---|---|---|
search | up to 120 characters | – | Title or shop ID starts with the term |
page | from 1 | 1 | Page |
per_page | 1 to 200 | 50 | Entries per page |
POST /api/v1/catalog
Creates a product. The response is 201 with product. If the shop ID is already taken, you get 422 with identifier_taken.
- Authentication
- Session token, Edit or higher
- Rate limit
catalog, 20 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
external_id | yes | up to 100 characters | ID in the shop |
title | yes | up to 200 characters | Title |
description | no | up to 800 characters | Description |
price_cents, list_price_cents | no | integer, 0 to 100000000 | Price and list price in cents; the list price is not checked against the price |
currency | no | exactly three characters | Currency, stored in capitals; EUR if omitted |
image_url, product_url, cart_url | no | URL, up to 500 characters | Image, product page and cart link |
is_available | no | true or false | In stock; true if omitted |
attributes | no | object | Free-form attributes; the previous value stays if omitted |
rating_tenths | no | 0 to 50 | Rating in tenths, 50 is the top rating; the output also carries rating, the tenths divided by ten |
rating_count | no | 0 to 100000000 | Number of ratings |
delivery_days | no | 0 to 365 | Delivery time in days |
features | no | up to 6 entries, each up to 60 characters | Features for the product card |
action | no | none, product, cart | Button on the product card; product if omitted |
accessories, also_bought | no | up to 6 shop IDs each, up to 100 characters | Accessories and frequently bought together |
A product is returned with the product card fields (id, external_id, title, description, price_cents, currency, price_text, list_price_cents, list_price_text, saving_cents, saving_percent, image_url, product_url, is_available, rating, rating_count, delivery_days, features, action, action_url), plus attributes, action_setting, rating_tenths, cart_url, accessories, also_bought and updated_at.
PUT /api/v1/catalog/{id}
Changes a product using the same fields as for creating, except external_id, which cannot be changed. The endpoint rewrites the product: an omitted field falls back to its default or null; only attributes is kept when omitted. Response with product; an unknown product gives 404 with not_found.
- Authentication
- Session token, Edit or higher
- Rate limit
catalog, 20 per minute
DELETE /api/v1/catalog/{id}
Deletes a product. The response is {"deleted": true}.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
POST /api/v1/catalog/import
Imports up to 500 products per call, in one transaction. Matching is by external_id: a second run with the same IDs creates no duplicates but updates the existing products. The response contains new, changed and total.
- Authentication
- Session token, Edit or higher
- Rate limit
catalog, 20 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
products | yes | 1 to 500 entries | Products with the fields used for creating |
Shop connection
The connection is read-only: nothing is created, changed or ordered in the shop through it. The access key goes in once; every response shows only its last four characters in access_key_masked. Reading, saving and syncing answer with connection, an object with the fields in the following table.
| Field | Content |
|---|---|
base_url | Shop address |
access_key_masked | Key ending, prefixed with an ellipsis |
currency, is_active | Currency and whether the connection is active |
sync_pending, sync_started_at, sync_requested_at | Pending sync, its start and when it was requested |
last_sync_at, last_sync_ok, last_sync_error, last_success_at | Last sync, its result, its error and the last successful one |
counts | Counts from the last sync or null |
GET /api/v1/catalog/shop
Returns the account’s shop connection in connection; if none is saved, connection is null.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
PUT /api/v1/catalog/shop
Saves the connection. The address is checked before it is stored, because the server later calls it itself with the key. If a changed address points at a different shop, a new key is needed. is_active changes only if it is sent. The response contains connection.
- Authentication
- Session token, Administration or higher
- Rate limit
catalog, 20 per minute
| Field | Required | Rule | Meaning |
|---|---|---|---|
base_url | yes | up to 300 characters, https | Shop address, for example https://shop.example.com |
access_key | on first save and for a new shop | 12 to 200 characters of letters, digits, _ and - | Access key |
is_active | no | true or false | Connection active |
| Status | error | Meaning |
|---|---|---|
422 | url_unreadable | Address cannot be parsed. |
422 | https_required, scheme_not_allowed | The address does not use https. |
422 | name_unresolvable | The host name cannot be resolved. |
422 | target_not_allowed | The address does not point to the public internet. |
422 | access_key_required | New shop without a new key. |
POST /api/v1/catalog/shop/test
Tests the saved connection with a sample request. Success: ok: true with currency and has_products. Failure: ok: false with error, for example key_unreadable or a reason as when saving. At most three tests per minute; after that 429 with too_many_tests, retry_after and the Retry-After header. Without a connection: 404 with connection_missing.
- Authentication
- Session token, Administration or higher
- Rate limit
catalog, 20 per minute
POST /api/v1/catalog/shop/sync
Requests a sync, which runs as a background job (see Background jobs). The response is 202 with job_id, already_pending and connection; a second click gets the pending job back, with already_pending: true. Once no job is pending, a new request is accepted 900 seconds after the last one at the earliest; before that, requests get 429 with sync_too_soon, retry_after, connection and the Retry-After header.
- Authentication
- Session token, Administration or higher
- Rate limit
catalog, 20 per minute
| Status | error | Meaning |
|---|---|---|
404 | connection_missing | No connection saved. |
422 | connection_inactive | The connection is switched off. |
DELETE /api/v1/catalog/shop
Removes the connection. The products stay in the catalogue. The response is {"deleted": true}; without a saved connection you get 404 with connection_missing.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
Analytics
The following endpoints up to and including GET /api/v1/analytics/report require the plan feature for conversation analytics; without it they answer 402 with plan_feature_missing. Any role may read them. In the app this is Analytics, described in Analytics. Test conversations with channel test, that is those from the test dialog and the check conversations of Acceptance, are not included in these figures, as in the app.
| Parameter | Values | Default | Effect |
|---|---|---|---|
period | 7d, 30d, 90d | 30d | Period in days; today counts |
zone | Time zone name, up to 64 characters | Europe/Berlin | Zone in which days are counted. Older names that browsers still report, such as Asia/Calcutta or Europe/Kiev, are accepted and calculated under today’s name (Asia/Kolkata, Europe/Kyiv); responses that carry zone give that name as well. Upper and lower case do not matter; zone gives the usual spelling. The fixed offsets Etc/GMT+1 to Etc/GMT+12 and Etc/GMT-1 to Etc/GMT-14 are accepted with the sign used by the time zone database: Etc/GMT+1 is one hour behind UTC. An unknown zone gives 422 with unknown_timezone, and so does an offset such as +01:00. The Analytics page then shows “The service does not recognise the time zone of this device. The figures cannot be assigned to days and are therefore not shown.” |
assistant | UUID | – | Only this assistant; an unknown one gives 404 with unknown_assistant |
GET /api/v1/analytics
Returns the period’s 32 metric cards in a single request: period, zone, from, to, days, assistant and cards.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
Each card has value, measured and reason. value is null when measured is false; reason then gives the cause. Rates carry value as a percentage with one decimal place, plus counter and denominator. coverage is an average expressed as a percentage and carries only denominator; messages_per_conversation, reply_ms_p50 and reply_ms_p95 carry neither.
| Group | Cards |
|---|---|
| Volume | conversations, messages, answered, messages_per_conversation |
| Handover and frustration | escalations, escalation_rate, self_resolved, frustration_rate |
| Answer quality | abstentions, unanswered, citation_rate, coverage |
Knowledge search, from the latest probe run in the period, for the whole account regardless of assistant | retrieval_mrr, retrieval_hit_rate |
| Rules and blocks | enforcement_rate, hard_topic_rate, blocked_rate |
| Speed and usage | reply_ms_p50, reply_ms_p95, units |
| Tools | tool_calls, tool_executed, tool_rejected, tool_errors |
| Cases | leads, appointments, carts, abandoned, abandon_rate |
| Visitor ratings, counted by rating time | visitor_ratings, visitor_helpful_rate, visitor_rating_coverage |
reason | Meaning |
|---|---|
no_conversation | No conversation in the period |
no_reply | No reply to compute the value from |
no_search | No knowledge search |
no_probe | No probe run in the period |
no_enforcement_check | No rule check |
no_tool_call | No tool call |
no_rating | No rating |
GET /api/v1/analytics/timeseries
Time series per day in the chosen zone. days holds, per day, day, conversations, messages and escalations, plus citation_rate, unanswered, escalation_rate, coverage and retrieval_mrr in the shape of the metric cards; the response also carries period, zone and measured.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/topics
The most frequent topics, at most 15. Each topic carries previous, its count in the equally long period immediately before, and direction (up, down or flat); if there was no topic before, previous gives the reason no_previous_question and direction is null. Without a visitor question in the period, measured is false with reason no_question; without a recognisable topic, with no_topic.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/distribution
Distribution of conversations by language (languages) and by window width (viewports: handy, tablet, desktop, breit), each group with its own measured. Each entry in entries has key, conversations and share; the share refers to basis, the conversations with this information. Conversations without it are counted separately under without. Without any conversation in the period, reason is no_conversation; if a group has no information at all, it is no_language or no_viewport.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/unanswered
Questions the assistant had no answer to: for the latest 50 fallback replies (fallback), the visitor question before each, masked and cut to 300 characters. questions holds conversation_id, question and when per question, plus clusters.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/gaps
Detected knowledge gaps, at most 50. Unlike unanswered, an explicit refusal is told apart from a reply without a source. If the question text is no longer available, the gap is still counted; question is then null and question_available is false. Each gap has kind (refused or uncited); refused, uncited and total count all gaps in the period.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/blocks
Blocked cases in the period, at most 50: what was turned away and where a limit applied, broken down by topic. Each case in cases has kind (blocked or enforced), topics, hard and when; topics counts per topic, plus checked, enforced, hard, blocked and total.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/dropoffs
Drop-off points and goal events with their position in the conversation; the position is the message’s sequence number, the first message being position 1. A drop-off is an open conversation that was not handed over, has at least one visitor message, has had no message for 30 minutes (minutes) and has no enquiry, appointment or cart. The response contains dropoffs, positions, goals with kind lead, appointment or cart, total and goals_total. The distribution covers all cases in the period, the list only the latest 50; total gives the full count.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/recommendations
One suggestion per anomaly in the period, with the figure and the area it concerns. It makes no statement about the effect of a change. Each entry in recommendations has kind, count and target (wissen, assistenten or werkzeuge); only kinds with at least one case are listed, the most frequent first. kinds gives the number of possible kinds, total the number listed.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/versions
The published versions of an assistant, highest number first, at most 50. The response contains assistant, versions with id, number and published_at, plus count and capped; period and zone have no effect here. Without assistant the endpoint answers 422 with assistant_required.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/comparison
Compares a window before a version was published with an equally long one after it, computed over the time and the assistant of that version. The window after ends at the time of the request at the latest; if it is shorter for that reason, after carries partial: true. The response contains version, comparable, before, after and delta; delta gives the difference and direction (up, down or flat) per card, not an effect. If one side has no measured value, the card shows side_not_measured. If the version is not published, comparable is false with reason not_published; if the publication lies in the future, with no_window. The version parameter (UUID) is required; an unknown version gives 404 with unknown_version.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/analytics/report
Builds a report from metrics, time series, topics, unanswered questions, gaps, blocked cases and drop-offs (metrics, timeseries, topics, unanswered, gaps, blocks, dropoffs) with its timestamp in created. The report is computed fresh, not taken from the cache.
- Authentication
- Session token, any role
- Rate limit
conversations, 30 per minute
GET /api/v1/analytics/subscription
Shows the weekly report: weekly_report (true if not set), period 7d, day monday and recipient, the owner’s email address if it is confirmed, otherwise null.
- Authentication
- Session token
- Rate limit
- no named throttle
PUT /api/v1/analytics/subscription
Subscribes to or unsubscribes from the weekly report. Only the owner can switch it, because the email goes to them. Logged as report.subscription.updated, response in the same shape as the read endpoint. Without an audit log entry the setting stays unchanged; you then get 500 with not_logged.
- Authentication
- Session token, Owner only
- Rate limit
- no named throttle
| Field | Required | Rule | Meaning |
|---|---|---|---|
weekly_report | yes | true or false | Weekly report on or off |
Cases
Cases are enquiries (leads), appointments (appointments) and carts (carts). In the app they are under Cases, described in Appointments and cases. Changing the status of appointments and carts requires the plan feature for appointments and enquiries; without it this endpoint answers 402 with plan_feature_missing. Reading and deleting work in every plan.
GET /api/v1/records
Counts cases in two groups: pending with the open ones, that is leads (new), appointments (requested) and carts (open), and total with all of them, split by the same three kinds. plan_locked lists the kinds without the plan feature, whose status cannot be changed; they are counted as well. available_from names the plan from which all locked kinds are included, available_from_name its name; both are null when nothing is locked or no such plan can be named.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
GET /api/v1/records/{type}
Returns one page of a kind, newest first, with entries, total, page and per_page. An unknown kind gives 404 with unknown_kind.
- Authentication
- Session token, any role
- Rate limit
- no named throttle
| Parameter | Values | Default | Effect |
|---|---|---|---|
type | leads, appointments, carts | – | Kind, in the path |
status | up to 20 characters | – | Only this state |
page | from 1 | 1 | Page |
per_page | 1 to 200 | 50 | Entries per page |
POST /api/v1/records/{type}/{id}/status
Sets the state of a case. The response contains entry. Setting an appointment to confirmed fires the appointment.confirmed webhook with id and starts_at, and the response also contains confirmation_mail. An unknown case gives 404 with not_found. If a cancelled appointment is set to requested or confirmed and its time has been taken in the meantime, the result is 409 with appointment_slot_taken. If the appointment lock of the account is busy while an appointment is set to requested or confirmed, the result is 503 with appointment_lock_busy and Retry-After: 1. In both cases the appointment stays unchanged.
- Authentication
- Session token, Edit or higher
- Rate limit
- no named throttle
DELETE /api/v1/records/{type}/{id}
Deletes a single case, for example when a visitor asks for their details to be erased, and logs this beforehand as record.deleted with kind, ID and counts, without name, email address or phone. Without an audit log entry the case stays; you then get 500 with not_logged. There is no plan check here: appointments and baskets can be deleted even without the appointment features. Together with the case, undelivered notifications about it to connected systems (lead.created, appointment.requested, appointment.confirmed) go, and for an appointment any reminders and confirmations not yet sent; their row in the delivery log is then set to failed. Visitor attachments stay with the conversation. With with_conversation=1 the conversation the case came from goes as well, with all messages and attachments. The response contains deleted, kind, id, conversation_deleted and counts. An unknown kind gives 404 with unknown_kind, an unknown case 404 with not_found.
- Authentication
- Session token, Administration or higher, not during an impersonation
- Rate limit
- 5 per minute and user, shared by export, retention periods, objections and deletion
| Kind | States | Fields in addition to id, conversation_id, status, created_at |
|---|---|---|
leads | new, seen, done | name, email, phone, subject, source, fields |
appointments | requested, confirmed, cancelled | type, starts_at, duration_min, name, email, phone, subject |
carts | open, claimed, expired | items, subtotal_cents, discount_cents, coupon_code, shipping_cents, total_cents, currency, pickup_token_set (only whether a pickup token is set, not the token) |
confirmation_mail | Meaning |
|---|---|
queued | Confirmation to the visitor queued |
unchanged | Appointment was already confirmed, no new email |
superseded | Appointment is no longer confirmed |
no_email | No email address on the appointment |
past | Appointment has already started, no confirmation is sent |
duplicate | A confirmation for this start time already exists |
failed | Queuing failed |
Audit log
GET /api/v1/activity
Returns the audit log, 25 entries per page, newest first. In the app this is Audit log; for entries with who_kind api_key it shows API key “{name}” under Who, with the name from who.
- Authentication
- Session token, Administration or higher
- Rate limit
- no named throttle
| Parameter | Values | Effect |
|---|---|---|
action | up to 80 characters | Only this action |
page | 1 to 1000 | Page |
| Field | Content |
|---|---|
entries | Entries with id, time, action, who, who_kind, subject_type, subject_id and data. who is the person’s email address, with who_kind user. If an entry records the triggering API key in data.api_key, for example when a knowledge source is created through the API, it holds the key’s name at the time of the action, or its prefix if there is no name, with who_kind api_key. Otherwise both are null. |
actions | The actions present in the log, for a filter |
total, page, pages | Number of entries, page and page count |
Among others, the endpoints on this page write conversation.revealed, conversation.exported, conversation.deleted, attachment.read, retention.updated, visitor.blocked, visitor.unblocked, team.invited, team.invitation_revoked, team.role_changed, team.member_removed, team.invitation_accepted, routing.rule_created, routing.rule_updated, routing.rule_moved, routing.rule_deleted and report.subscription.updated.
Frequent questions
Why are texts masked in the conversation list?
The list and the single view mask email addresses, phone numbers, IBANs and postal addresses, and so does the export unless it is requested with reveal=1. Only the owner can fetch a conversation in plain text, via POST /api/v1/conversations/{id}/reveal with a reason of at least five characters; the access is logged.
What does search_too_broad mean?
Without plain-text rights the search runs on the masked version. If it finds more than 20000 conversations there, the endpoint stops with 422. Set from and to or more filters, then search again.
Why does analytics still show the old figures after a change?
The analytics endpoints keep their response for 60 seconds. After that, the next request computes afresh. The report at GET /api/v1/analytics/report is computed fresh.
Why can I not write into a widget conversation?
Only whoever has taken over a live visitor conversation can write into it. Call POST /api/v1/conversations/{id}/takeover first; without a takeover the send endpoint answers 409 with not_taken_over.
What happens to the products when I remove the shop connection?
They stay in the catalogue. DELETE /api/v1/catalog/shop removes only the connection; delete products one at a time with DELETE /api/v1/catalog/{id}.
Ask it yourself, Assistant of this website
Automated assistant