Skip to content
XICBOT

Settings

Deutsch
Documentation · Operations and security

Data security and tenant isolation

How XICBOT keeps accounts apart, checks roles, masks personal data, logs plain-text access and limits outgoing requests to public targets.

In plain language

This page explains how your data is protected. The data of one account is kept apart from all other accounts. Each role may only do certain things. Names and other personal details in chats are hidden. The page says who may see the full text. Each such access is written down. Requests to the outside only go to public targets.

Last updated

All documentation topics

This page explains how the app keeps one account's data apart from every other account, which role may do what, how personal data in conversations is masked, who can see the plain text, how keys are stored and which targets outgoing requests may reach. It is written for account owners and administrators.

Tenant isolation

Each account is a separate tenant. Isolation lives in the data layer rather than in individual screens: every query on account data is restricted to the account of the current request, and new records are assigned to that account automatically. If no account can be determined, the query returns no rows instead of reading across account boundaries.

Where the account of a request comes from depends on the access path:

Access pathAccount comes fromResponse if the account is not active
App, signed inthe sign-in; with several accounts, the one chosen via Switch account403 with tenant_ plus the status, for example tenant_suspended; text “Das Konto ist derzeit nicht aktiv.”
Widgetthe widget key in the embed code404 unknown_widget, the same response as for an unknown, disabled or paused assistant
Interfacethe API key in the X-Api-Key header403 tenant_suspended

The status is checked again on every request. If an account is suspended, access ends for sessions that are already open as well. The app then shows “This account is locked. Please write to mail@xicbot.de.” A new sign-in to an inactive account is rejected with 403 and recorded under Recent sign-ins with the result blocked. This list shows at most 50 entries, newest first; more in Account and security.

Roles and permissions

Roles are ranked. A function requires a minimum role, and every higher role may use it as well. The Owner role is not assigned in team management. How roles are assigned is covered in Team and roles.

Role in the appKeyRank
Readviewer10
Editeditor20
Administrationadmin30
Ownerowner40

These minimum roles apply to the topics on this page:

FunctionMinimum role
Read conversations and download them masked as CSVRead, plus a confirmed email address
API keys and webhooks under InterfacesAdministration
Read the Audit logAdministration, plus a confirmed email address
Request plain text, including for CSV export via the interfaceOwner, plus a confirmed email address
Change an assistant's Handover addressOwner

If a required email confirmation is missing, the interface responds with 403 email_unverified. If the role is missing, it responds with 403 role_too_low and names the required role in the required field; for the Handover address it also returns handoff_email in the field field. The app shows a message that depends on the required role:

RequiredMessage in the app
editor“With the Read role this area is view-only. Changes need at least the Edit role.”
admin“This needs at least the Administration role.”
owner“Only the account owner can do this.”

Switching between accounts

If you belong to several accounts, use Switch account. Roles apply per account: after switching you work with the role you hold in the target account. An account that does not exist and an account you do not belong to produce the same 404 not_found response, so the response does not reveal whether someone else's account exists. A suspended target account returns 403 with tenant_ plus the status. Switching also works from within a suspended account.

A switch is normally recorded in the audit log as tenant.switched, together with the previous account and the role in the new one. Switching is blocked while the operator has taken over the account. More on multiple accounts in Team and roles.

POST /api/v1/tenant/switch

Switches to another account of the same person. Field tenant (required, string of up to 64 characters). Responses: 404 not_found for accounts that belong to someone else or do not exist, 403 with tenant_ plus the status for a suspended target, 403 in_impersonation during a takeover.

Authentication
Sign-in token; runs without checking the status of the current account
Rate limit
separate rate limit for account switching, see API limits

Sign-in and sessions

Each sign-in receives its own sign-in token valid for 30 days. The app keeps it in the browser's storage (localStorage) under xicbot.marke. Signing out deletes only the token of that device; sign-ins on other devices remain active.

  • The device list shows valid sign-ins, at most 50, newest first. Expired tokens are no longer listed.
  • Every sign-in other than your own can be ended individually with End, or all at once with End all others. Operator takeover sessions are left in place by End all others; they can be ended individually. API keys also remain valid; their number is normally recorded in the account.sessions_ended audit log entry.
  • Changing the password ends sessions; the number is normally recorded in the account.password_changed audit log entry.
  • Signing in, signing out, ending one session and ending all other sessions normally appear in the audit log as account.login, account.logout, account.session_ended and account.sessions_ended. If this ends an operator takeover, because it is ended with End in its row or someone signs out from within it, the audit log records impersonation.end instead, see Operator takeover.

Masking in conversations

Conversations are stored as they took place. Masking happens on output: the app shows personal data replaced by placeholders and notes in the conversation “Personal data is masked: email, phone, IBAN and postal address.” The interface lists the kinds found in a conversation in the masked_kinds field.

KindPlaceholderDetection
Email[E-Mail]addresses with @ and a domain
Phone[Telefon]starts with + and a country code or with 0, at least 7 digits; spaces, slashes, brackets and hyphens count as separators, dots do not
IBAN[IBAN]two letters and two digits at the start; checked before phone numbers
Postal address[Adresse]street name with an attached German suffix such as -straße, -str., -weg, -allee, -platz, -gasse, -ring, -damm, -ufer, -chaussee or -steig plus a house number; postcode and town only at the start of a line or after a separator

The placeholders appear in their German form, including in the English interface. If a text cannot be checked, [nicht maskierbar] is shown in its place: when in doubt, the text is masked. Masking applies to:

  • the text of every message in the history
  • the conversation title in the list and the detail view
  • the content of tool calls, except pure structure fields
  • questions in Analytics, which are also cut to 300 characters
  • the handover reason in the conversation view
  • the question in gap.detected and the reason in handoff.triggered sent to your webhooks, see API keys and webhooks
  • the values under Uncovered claims for an answer
  • both CSV files, see CSV export

The email sent to the Handover address when a conversation is handed over is not masked: it contains up to the last 200 messages of the conversation verbatim. Only the Owner can change this address.

Searching the conversation list uses the plain text for the owner and the masked text for every other role. A search for a phone number therefore finds nothing for these roles that they are not allowed to see. If such a search matches more than 20,000 conversations, the interface responds with 422 search_too_broad; narrow the date range or filters. More in Conversations.

Requesting plain text

Only the owner can request the plain text of a conversation. For other roles the app shows the note “Only the account owner can request the plain text – with a reason, and every access is logged.”

  1. Step 1

    Open the conversation

    Open the conversation under Conversations.

  2. Step 2

    Request plain text

    Select Request plain text.

  3. Step 3

    Give a reason

    Under Reason for this access, enter why you need the plain text: at least 5 and at most 200 characters. There is no default. If the reason is too short, the app shows “Please give a reason of at least five characters.”

  4. Step 4

    Confirm

    Confirm with Show plain text. The conversation appears unmasked, headed by “Plain text visible – this access is logged.”

Log entry first, data second

Before any plain-text output, the audit log entry conversation.revealed is written with the reason and the number of messages. If the entry cannot be written, the plain text stays hidden and the app reports “Not saved: the entry in the audit log could not be written. Please try again.”

The role is checked again with every request. The plain text applies to this view only: when the conversation is reloaded it appears masked again, and another access needs a new reason. If the role is no longer there, the app shows “Only the account owner can request the plain text.”

List of plain-text access

Each conversation lists the last 20 accesses under Plain-text access, with time, the person's email address and Reason. Every role that can open the conversation sees this list. If there has been no access yet, it reads No plain-text access so far.

POST /api/v1/conversations/{id}/reveal

Returns a conversation in plain text. Field reason (required, 5 to 200 characters). The response matches the detail view, with masked: false. Errors: 422 if the reason is missing or shorter than 5 characters, 403 role_too_low without the owner role, 404 not_found for an unknown conversation, 500 not_logged if the audit log entry fails.

Authentication
Sign-in token, owner role
Rate limit
shared with the other conversation endpoints, see API limits

CSV export

The app offers two CSV files, both with masked content. Every export writes the audit log entry conversation.exported before the file is produced. It records whether the export is in plain text, the reason and the number of rows, plus the filter used for the list and, for a single conversation, whether it was cut off. If this entry fails for a plain-text export, no file is returned; a masked export continues because it discloses nothing.

FileButtonScopeColumns
conversations.csvFiltered conversations as CSVconversations matching the current filter, up to 5,000; beyond that 422 too_many and “Too many conversations for one export. Please narrow the date range.”id, created_at, assistant, channel, language, status, messages, title, escalation
gespraech-<id>.csvThis conversation as CSVone conversation; very long histories are cut off at 500 messages and 500 tool callstime, role, text, state, rating, tool_key, tool_state
  • The name of the single-conversation file contains only the conversation ID, not its title.
  • If a field starts with =, +, -, @ or a tab, an apostrophe is prepended so that spreadsheet software does not evaluate it as a formula.
  • Line breaks inside a field are replaced with spaces. The separator is a semicolon.
  • In conversations.csv, the title and the handover reason are masked.

Via the interface, the owner can retrieve a plain-text export using the parameters reveal=1 and reason (5 to 200 characters). Without the owner role the response is 403 role_too_low; without a sufficient reason it is 422 reason_required. If your plan does not include export, both the list and the single-conversation file return 402 plan_feature_missing, masked or not. The response carries the headers x-rows (number of rows), x-masked (1 masked, 0 plain text) and, for the single-conversation file, x-capped (1 if cut off). The app itself downloads masked files only. More in Conversations and API: conversations and management.

GET /api/v1/conversations/export

Filtered conversation list as CSV, up to 5,000 conversations. Optional reveal (0 or 1) and reason (up to 200 characters).

Authentication
Sign-in token; plain text only with the owner role
Rate limit
see API limits

GET /api/v1/conversations/{id}/export

History of one conversation as CSV, cut off at 500 messages or 500 tool calls. Parameters as for the list.

Authentication
Sign-in token; plain text only with the owner role
Rate limit
see API limits

Operator takeover

To help with questions about an account, the XICBOT operator can take it over. The takeover uses the highest-ranked login currently working in this account: Owner first, then Administration, Edit and Read, and the oldest login where roles are equal. A member currently working in another account is not considered. The password of that login is neither read nor changed. The takeover runs on a separate sign-in token that expires after 4 hours. The operator starts it with Take over account and can give a reason of up to 200 characters under Reason for the takeover. The start, with the reason and expiry time, is recorded in the operator's action log and in your account's audit log; if either entry cannot be written, the takeover does not begin. The end is usually recorded in both logs as well: when the operator closes the takeover with End takeover or signs out within it, when you end it in the device list, and when it ends because all sessions of the login end, for example when the password is reset via the link, when the operator resets two-factor sign-in or when the login is removed from the team. An ongoing takeover also ends when the login taken over switches to another account itself; its end is then usually recorded in the audit log of the account where it began and in the operator's action log. If both sides end the takeover at the same moment, the end appears only once in each log. Only when it expires on its own after 4 hours is no end entry written; the expiry time is then part of the start entry. An account without any login cannot be taken over.

  • In the device list of the login that was taken over, the session appears as Takeover by the operator. It can be ended early there with End; End all others leaves it in place.
  • Changes made during the takeover appear in the account's Audit log under the login that was taken over.
  • When the current login is queried, the interface reports a running takeover in the impersonation field, with the operator's email address (operator) and the expiry time (expires_at).

During a takeover, functions that could lock the login out or weaken it are blocked. The interface responds to them with 403 in_impersonation. The block applies only to requests made from within the takeover; your own sessions are not affected:

BlockedMessage in the app
Change password“The password cannot be changed during a support session.”
Set up, confirm or turn off two-factor sign-in“This area is locked during an impersonation session.”
Add or remove security keys“This area is locked during an impersonation session.”
End all others“This area is locked during an impersonation session.”
Switch account“This area is locked during an impersonation session.”
Operator area“This area is locked during an impersonation session.”

During the takeover, the operator sees Takeover with their address in the status bar, and screen readers hear “You are working inside a customer account.”; it ends with End takeover. This deletes the takeover's sign-in token; if it is not ended, it expires after 4 hours. A request to end a takeover when none is running returns 400 no_impersonation.

Passwords, keys and secrets

ItemStorageDisplay
Passwordas a hashcannot be retrieved
Two-factor secretencryptednot returned by the interface
API key, starts with xbot_hash only; the first 12 characters remain readable as an identifierplain text once on creation, in the plaintext_once field
Webhook secret, starts with whsec_encryptedonce on creation, in the secret_once field
Shop connection access keyencryptedonly … and the last 4 characters
  • Each account can have at most 10 active API keys at a time. Beyond that the interface responds with 422 too_many_keys and the app with “The maximum number of simultaneous keys has been reached.”
  • The API key belongs in the X-Api-Key header. If it is passed in the URL as the api_key or key parameter, the response is 400 key_in_url. If the header is missing or its value does not start with xbot_, the response is 401 key_missing.
  • Unknown, revoked and expired keys all get the same 401 invalid_key response, so the response does not reveal whether a key was ever valid.

Creating and revoking keys is described in API keys and webhooks and API authentication, the shop connection in Catalog and shop.

Outgoing requests

Several functions fetch addresses outside XICBOT: webhooks, tools with their own interface, website sources in the knowledge base, the shop connection and sources created via the interface. The same target check applies to all of them:

  • Addresses must use http or https.
  • The host name is resolved for IPv4 and IPv6. Every address found must be on the public internet; private, link-local, reserved and loopback ranges are blocked. IPv4 addresses written in IPv6 notation are checked as well.
  • The check runs when the target is saved. Webhooks, tools, website sources and the shop connection are checked again right before each request.
  • Webhooks and tools connect only to the addresses that have just been checked, and they do not follow redirects.
  • Website sources follow redirects but check each new target on its own.

When saving, the response is 422 with one of the following reasons in the error field. Under Interfaces, the app shows “This address is not permitted as a target.” for all four reasons. Under Triggers › Tools, it gives the reason at the Interface address field, for example “The host name of this address cannot be resolved. Please check the spelling.”; all sentences used there are listed under Which addresses are allowed. The delivery list of a webhook and the test delivery show the individual messages:

ReasonMeaningMessage for deliveries
url_unreadablethe address cannot be parsed“The address cannot be read.”
scheme_not_allowedneither http nor https“This address type is not supported.”
name_unresolvablethe host name resolves to no address“The host name of the address cannot be resolved.”
target_not_allowedat least one address is not on the public internet“This target is not allowed, for example an internal address.”
redirect_not_followedthe target redirected (webhooks only)“The target redirected. Redirects are not followed – please enter the final address.”

Timeouts and sizes

A tool with its own interface sends its request as a POST with JSON. The timeout is 8 seconds; a tool's deadline_s value is clamped to 1 to 20 seconds, and connecting may take at most 5 seconds. Responses larger than 64 KiB are cut off. If an access key is stored, it is sent as Authorization: Bearer. Webhooks also time out after 8 seconds. More in Tools and Knowledge.

Error responses

Error responses from the interface contain no internal details such as exception type, file, line or stack trace. For status codes of 500 and above, any message text is replaced with “Serverfehler”; a response without a text does not get one, so not_logged reads just {"error":"not_logged"}. Messages about input errors with status codes below 500 are kept so they can be matched to the affected field.

Audit log

The Audit log records who did what in the account: each entry contains the time, the action, the person's email address, the affected item and additional details. It covers a defined set of actions, not every page view and not every read. Logins with at least the Administration role and a confirmed email address can read it. How the list is laid out and how to filter and page through it is described in Team and roles. These actions appear in the audit log, each under its label in the app:

AreaActions
Sign-inSigned in, Signed out, Session ended, All other sessions ended, Password changed (with the number of sessions ended), Password reset, Two-factor enabled, Two-factor disabled, Security key added, Security key removed
AccountAccount created, Account confirmed, Confirmation email resent, Account switched (with the previous account and the role in the new one), Account deleted
TeamPerson invited, Invitation revoked, Invitation accepted, Role changed, Member removed
AssistantsAssistant created, Version published, Version restored, Assistant deleted, Widget key renewed, Check run performed
Tools and routingTool created, Tool changed (noting whether the minimum role, the target address or the access key changed), Tool deleted, Tool chain capped, Routing rule created, Routing rule changed, Routing rule moved, Routing rule deleted
KnowledgeKnowledge source created, Knowledge source changed, Knowledge source re-read, Knowledge source deleted, Knowledge version restored, Knowledge recomputed, Question-answer pair created, Question-answer pair changed, Question-answer pair deleted, Probe question created, Probe question deleted, Probe questions run, Search weights changed, Search weights tuned
Conversations and visitorsConversation content viewed (with the reason and the number of messages), Conversation exported (with details on plain text, reason and row count), Conversation deleted, Record deleted, Attachment stored, Attachment opened, Visitor blocked, Visitor block lifted
Legal and dataRetention periods changed, Retention period applied, Data exported, Attachments exported, Data processing agreement accepted, Objection to subprocessor, Report delivery changed

For many changes the entry is written in the same transaction as the change itself: when a conversation is deleted, for tools and routing rules, for changes to the team, to knowledge sources, question-answer pairs and search weights, when a knowledge version is restored, for the second factor, for security keys and when a visitor is blocked. If the entry cannot be written, the previous state is kept; when a conversation is deleted, the app then reports “Not saved: the entry in the audit log could not be written. Please try again.” Exempt are actions that should not fail because of a logging error: creating and confirming an account, resending the confirmation email, signing in and out, ending sessions, changing or resetting the password, switching accounts, creating or re-reading a knowledge source, recomputing knowledge, running probe questions and check runs, and storing visitor attachments. They succeed even if the entry cannot be written; in that case the entry is missing. A visitor attachment, by contrast, is only delivered if the download is recorded in the audit log. How long entries are kept is described in Retention and deletion.

A takeover by the operator appears in this log with its start and end: as Takeover by the operator started, with the reason (reason) and expiry time (expires_at) under Details, and as Takeover by the operator ended, with ended_by under Details: operator if the operator ended it, otherwise customer. If it ends because the password is reset via the link, or because the login is removed from the team or switches to another account itself, Details also shows the cause as cause: password_reset, member_removed or tenant_switched. The operator's action log records start and end as well; you can also spot a running takeover in the device list, see Takeover by the operator. Whatever is changed during a takeover appears in the audit log under the login that was taken over.

Frequent questions

Can I see who changed something in the account?

Yes, in the Audit log, available from the Administration role with a confirmed email address. It records a defined set of actions, such as sign-ins, changes to the team, assistants, tools and knowledge, plain-text access, exports and deletions, each with the time and the person's email address. A takeover by the operator is listed there as Takeover by the operator started and Takeover by the operator ended; you can also spot a running takeover in the device list.

Who can read conversations in plain text?

The Owner, and only with a reason of at least five characters. Each access is logged before anything is shown and appears in the conversation under Plain-text access. All other roles see email addresses, phone numbers, IBANs and postal addresses masked.

Why does my CSV file show placeholders instead of phone numbers?

The app's CSV files are masked in the same way as the conversation view. A plain-text export is reserved for the owner via the interface, using reveal=1 and a reason; it is recorded in the Audit log.

What happens if I enter an internal address as a webhook?

The address is rejected when you save it, and the app shows “This address is not permitted as a target.” If a host name later points to an internal address, the check before delivery stops the request; the delivery list then shows “This target is not allowed, for example an internal address.”

What does the operator see during a takeover?

The operator works with the permissions of the login that was taken over. Plain text of a conversation is visible only if that login may request it, and then with a reason and an entry under Plain-text access. The operator cannot change the password, two-factor sign-in or security keys.

Something still unclear?

Tell us which page and which step you mean. We reply personally.

Enquiry in three steps

  1. 1. Topic
  2. 2. Your case
  3. 3. Contact

What is this about?

One click is enough, everything else is optional.

Tell us briefly about your case

Everything in this step is optional. It helps us reply with something concrete straight away instead of asking back.

Enquiries per month, roughly (optional)
When should it start? (optional)
How can we reach you?

Name and email address are enough. We reply personally.

We use your details to handle your enquiry. More on this in the privacy policy.