API authentication: sessions, keys and permissions
Session tokens, API keys and the chat window key: how to authenticate against the API, which scopes, roles and caps apply, and which errors you get back.
In plain language
Anyone who uses the API must prove who they are. There are several ways to do this. The app itself uses a token from the sign-in. Your own systems use a key. The chat window has its own public key. Each way only opens its own part. After that, the role and the plan also count. The page explains each step and each refusal.
All documentation topics
The API at https://app.xicbot.de/api/v1/ accepts three kinds of credentials, and each one opens only its own branch: the session token you get from signing in covers everything the application itself does, an API key connects your own systems under /api/v1/integration/, and the chat window uses its own public key. After that, the account status, a confirmed email address, the role, the plan and the key's scopes decide whether a request gets through. This page walks through each of these checks and the rejections they produce.
The three credentials at a glance
| Credential | How it is sent | Opens | Valid for |
|---|---|---|---|
| Session token | Header Authorization: Bearer <token> | the application routes under /api/v1/ | 30 days from sign-in; an operator impersonation 4 hours |
| API key | Header X-Api-Key: xbot_… | only /api/v1/integration/… | until the chosen expiry or until revoked; without an expiry it stays valid |
| Chat window public key | in the path: /api/v1/public/widget/{key}/… | only the chat window routes of that one assistant | until a new key is generated |
The credentials are not interchangeable. Under /api/v1/integration/ only the X-Api-Key header counts; a session token sent there gets 401 with key_missing. Conversely, an API key is not a session token: sent in the Authorization header to an application route it gets 401. Routes that need no credentials at all (sign-in, status page, enquiry form, demo, legal texts, invitation, cart) are covered in Public endpoints.
There is one version of the API, v1, and it is part of the path. An address such as /api/v2/… gets the same answer as a made-up one: 404 with route_unknown. If the path exists but not for the method you used, the answer is 405 with method_not_allowed, and allowed lists the methods the path accepts. You do not need an Accept header; under /api/ the API answers in JSON, including the 401 for missing credentials. Response and error format: API basics.
Session token from signing in
POST /api/v1/login with email address and password returns token, expires_at and user on success. Every sign-in gets its own token, valid for 30 days. It is named after the browser's or program's identifier (User-Agent, up to 190 characters), or sitzung if there is none. Send the token with every further request as Authorization: Bearer <token>. The individual sign-in fields are listed under Public endpoints.
422withbad_credentials: the address or the password is wrong.428withtotp_requiredorwebauthn_required: a second factor is set up. The response carrieschallenge,expires_in(600 seconds) andmethods, and if a security key is registered also awebauthnobject withchallenge,rp_id,allowandtimeout; see Account security.422withbad_totp: the second-factor code is wrong.422withchallenge_expired: the challenge from the428response is no longer valid, because the 600 seconds have passed, because the second step comes from a different IP address than the first, or because the user no longer has a second factor.422withbad_webauthnandreason: the sign-in with the security key was not accepted, andreasonsays why.422withwebauthn_not_offered: the second call sends a key signature although the428response had nowebauthninmethods.503withwebauthn_unconfigured: signing in with a security key is currently not available.429withtoo_many_attemptsandretry_in: too many attempts, see API limits.403withtenant_paused,tenant_suspendedortenant_deleted: the account is not active and the user belongs to no other active account; no token is issued. If the user belongs to another active account, sign-in logs them in there, an account they own first, otherwise the one they have belonged to longest; the switch is logged there withcauselogin, and a running impersonation ends. This answer only comes after correct credentials and a passed second factor, and unlike on later requests it has nomessagefield.
GET /api/v1/me shows what a token is allowed to do. What counts are user.role, user.email_verified and user.operator together with tenant.status, tenant.plan and tenant.features; a user without an assigned account gets tenant as null. The route and all of its fields are described in Account API.
Shell command bash
curl -s https://app.xicbot.de/api/v1/me \
-H "Authorization: Bearer <token>"If the token is missing, expired or deleted, every route that needs credentials answers 401. The application keeps its token in the browser's local storage; on a 401 it ends the session and the sign-in page shows Your session has expired. Please sign in again. POST /api/v1/logout deletes exactly the token it is called with. Called with an impersonation token, this counts as the end of the impersonation: the log shows impersonation.end, not a sign-out. Sign-ins on other devices stay in place.
POST /api/v1/invitation/accept accepts an invitation as long as there is no user account for the invited address yet; a deleted account does not count. It is restored and starts like a new one: with the password from this request, without two-factor authentication, without security keys and without earlier tokens. You set a password of at least 12 characters with letters and digits, confirmed in password_confirmation. The response comes with 201 and a token valid for 30 days, named einladung; it carries tenant in addition to token, expires_at and user. Without a log entry neither the account nor the membership is created, the invitation stays open, and the response is 500 with not_logged. If a user account that has not been deleted already exists for the address, the answer is 409 with login_required and email. In that case, sign in and accept the invitation with POST /api/v1/team/invitations/accept; this route does not require a confirmed email address, and an address that is still unconfirmed counts as confirmed once the invitation is accepted. It answers 403 with wrong_account if you are signed in with a different address than the invited one. Without a log entry the account does not join, and the response is 500 with not_logged. Inviting people and assigning roles: Team and roles.
API keys for your own systems
An API key belongs to the account and opens only /api/v1/integration/. It consists of xbot_ followed by 48 random letters and digits and goes into the X-Api-Key header; keys cannot be sent as Authorization: Bearer. The application puts it as A key is shown in plain text exactly once. Only its fingerprint is stored. What is kept is the SHA-256 fingerprint and the first twelve characters, which remain visible as Identifier so you can tell keys apart in the list.
The API checks a key in a fixed order; the first step that fails produces the answer:
| Step | Case | Status | Error |
|---|---|---|---|
| 1 | api_key or key in the address | 400 | key_in_url |
| 2 | X-Api-Key header missing, empty or not starting with xbot_ | 401 | key_missing |
| 3 | key unknown, revoked or expired; all three get the same answer | 401 | invalid_key |
| 4 | account not active, whether paused, suspended or deleted | 403 | tenant_suspended |
| 5 | the account's plan lacks the feature for keys and webhooks, for example after a plan change | 402 | plan_feature_missing |
| 6 | per-minute or per-day cap reached | 429 | too_many_requests |
| 7 | the route needs a scope the key does not carry | 403 | read_only or scope_missing |
Per-minute and per-day caps
Every key has a per-minute cap taken from Requests per minute and a daily cap whose value is set on the account; both are counted per key. Once either is reached, the API answers 429 with too_many_requests and gives the wait time in retry_after and in the Retry-After header. Values, time windows, how requests are counted and a sample response are in API limits.
To try out a new key, call GET /api/v1/integration/status. It needs no scope, reads no data and returns the account, the scopes and the per-minute cap of the key. Response and example are in Integration endpoints.
Key scopes
The scopes decide which routes a key can reach. Each scope means exactly what it says: leads.write does not include leads. To create and read enquiries you need both. The application puts it as Writing is a separate scope. Without it a key only reads. Paths are relative to /api/v1/integration/:
| Scope | In the application | Routes |
|---|---|---|
conversations | Read conversations | GET conversations, GET conversations/{id} |
leads | Read enquiries | GET leads |
leads.write | Create enquiries | POST leads |
appointments | Read appointments | GET appointments |
analytics | Read analytics | GET analytics |
knowledge.write | Create knowledge sources | POST knowledge/sources |
chat | Use chat | POST chat |
| no scope needed | - | GET status |
If the key lacks the required scope, the route answers 403 and required names the scope. If it ends in .write the error is read_only, otherwise scope_missing.
A key's scopes, name and per-minute cap cannot be changed once it exists; there is no route for that. For different scopes, create a new key and revoke the old one.
Managing API keys in the application
Keys are created, listed and revoked on the Interfaces page at https://app.xicbot.de/schnittstellen, from the Administration role upwards. Creating a key requires the plan to include the feature; otherwise POST /api/v1/keys answers 402 with plan_feature_missing. Listing and revoking work without the feature, so that a key from before a plan change can still be revoked; it cannot be used without the feature. The routes GET /api/v1/keys, POST /api/v1/keys and DELETE /api/v1/keys/{id} require the same role but no confirmed email address. At most ten keys can be valid at the same time, and only the response to the create call contains the plain-text key. Fields, steps, the list and revoking are covered in Keys and webhooks; which plans include the feature is shown in Plans and quotas and on the pricing page.
The chat window's public key
Every assistant gets a public key of 32 hexadecimal characters when it is created. It is not sent in a header but in the path: /api/v1/public/widget/{key}/…. This branch needs no session. On the assistant, the application shows it under Public key with the note It appears in plain text on every foreign page. It only permits using this one chat window.
Four cases get the same answer, 404 with unknown_widget: the key is unknown, Enable chat window is off, the assistant is not active (that is, it is set to Draft or paused), or the account is not active. From the outside there is no way to tell which case applies. A new assistant starts with Enable chat window switched off. If none of the four cases applies and the assistant belongs to a demo account whose period has run out, the answer is 404 with demo_expired.
Generate a new key replaces the key after the prompt Generate a new key? The current embed code stops working immediately. Then The new key is active. Please replace the embed code everywhere. is shown. The Administration and Owner roles can do this; everyone else sees Generating a new key needs the Administration or Owner role. The embed code is covered in Embed the widget, the routes of this branch in Widget endpoints.
Through the API, POST /api/v1/assistants/{id}/key swaps the key. The route's requirements, response and errors are described in Knowledge and assistant endpoints.
What a route additionally requires
After the credential itself, the API checks further conditions per route group, on every request:
| Routes under /api/v1/ | Requires |
|---|---|
Sign-in and registration, public/status, enquiry form, demo, legal texts, invitation, cart | no credentials |
public/widget/{key}/… | public key of an enabled, active assistant in an active account |
integration/… | API key and plan feature; every route except status needs a scope |
Account routes such as me, logout, password, sessions, second factor | session token, active account |
keys, webhooks | session token, active account, Administration role; plan feature for creating, changing and testing |
impersonation/stop | session token, active account |
knowledge, einrichtung, assistants, billing, privacy, conversations, blocks, team, routing, tools, catalog, analytics, records | session token, active account, confirmed email address |
activity | additionally the Administration role |
admin/… | session token, active account (an operator without an account of their own passes as well), confirmed email address, operator access, no impersonation in progress |
Individual routes can also require a role or a plan feature; that is noted with the route in Knowledge and assistants and Conversations and management. The rejections on session routes and what the application shows for them:
| Status | Error | Meaning | Message in the application |
|---|---|---|---|
401 | - | token missing, expired or deleted | sign-out, then Your session has expired. Please sign in again. |
403 | no_tenant | the user has no account | Your account does not have access to this area. |
403 | tenant_paused, tenant_suspended, tenant_deleted | the account is not active; message reads „Das Konto ist derzeit nicht aktiv.“ | This account is locked. Please write to mail@xicbot.de. |
403 | email_unverified | the email address is not confirmed | This area stays locked until the email address is confirmed. |
403 | role_too_low with required | the role is not high enough | for editor: With the Read role this area is view-only. Changes need at least the Edit role. For owner: Only the account owner can do this. Otherwise: This needs at least the Administration role. |
402 | plan_feature_missing with feature, plan, plan_name, available_in | the plan does not include the feature | Your {tarif} plan does not include this feature. See Billing for the overview. |
403 | not_operator | no operator access | This area is restricted to the operator. |
403 | in_impersonation | locked during an impersonation | This area is locked during an impersonation session. |
The account status (active, paused, suspended or deleted) is checked on every request, not only at sign-in. So once an account is paused, even a token that has not expired gets 403. Exempt are POST /api/v1/tenant/switch, so that you can switch from an inactive account into another one, and POST /api/v1/logout and POST /api/v1/impersonation/stop, which only end the token they are called with. Without a confirmed email address the account routes, key management and impersonation/stop remain reachable, the functional areas do not; how to confirm the address is explained in Sign-in and access.
Roles
| Role in the application | Value in role | Rank |
|---|---|---|
| Read | viewer | 10 |
| Edit | editor | 20 |
| Administration | admin | 30 |
| Owner | owner | 40 |
A route with a role condition requires at least the named rank; higher roles get through as well. An unknown role on your side counts as 0, an unknown required role as 99, so both end in a rejection: 403 with role_too_low and required. Some routes require the Owner role, for example the data export with GET /api/v1/privacy/export. What each role can do in the application is covered in Team and roles.
The role belongs to the combination of user and account. Switching into another account with POST /api/v1/tenant/switch gives you the role you hold there. An account the user does not belong to gets the same answer as an unknown one: 404 with not_found. If the target is not active, the answer is 403 with tenant_<status> and „Das Konto ist derzeit nicht aktiv.“ During an impersonation, switching is blocked (403, in_impersonation).
Operator access and impersonation
Operator is not a role but a separate flag on the user (user.operator). An Owner is therefore not an operator. The routes under /api/v1/admin/ require this flag and a confirmed address; without the flag they answer 403 with not_operator, during an impersonation 403 with in_impersonation. In the navigation, Operations in the Operator group appears only for operators and not during an impersonation.
For troubleshooting, the operator can take over an account: in the application with Take over account and the Reason for the takeover field, through the API with POST /api/v1/admin/tenants/{id}/impersonate and an optional reason. The reason is cut off after 200 characters. The takeover creates a separate token for the highest-ranking user currently working in this account, and among equal ranks for the one registered first; a member currently working in another account is not considered. It is valid for 4 hours, its name starts with impersonation:, and no password is read or set in the process. The start is logged together with the reason, the end without it.
- The response carries
token,expires_at,user(id,email,name) andtenant(id,name). An unknown account gives404withunknown, an inactive one409withtenant_inactive, an account in which no one is currently working422withtenant_without_user. - With the impersonation token,
GET /api/v1/mereturns the operator's address (operator) andexpires_atunderimpersonation. The application then shows Takeover in the status bar, with that address and the End takeover button; screen readers hear You are working inside a customer account. there. - Sign out during an impersonation ends it like End takeover; in the application the operator is then signed out of their own account as well.
- In the session list under Account in Signed-in devices, the takeover appears as Takeover by the operator. End all others (
DELETE /api/v1/sessions) leaves it and the account's API keys in place; the customer ends it with End in its row (DELETE /api/v1/sessions/{id}). - Blocked with
403andin_impersonation: changing the password (in the application The password cannot be changed during a support session.), ending all other sessions at once, setting up, confirming or removing the second factor, adding or removing security keys, switching accounts and the routes under/api/v1/admin/. Of the single sessions (DELETE /api/v1/sessions/{id}), an impersonation can only end itself; any other session also returns403within_impersonation. - The per-minute caps per user, for example for assistants or for creating keys, do not apply during an impersonation, because the requests would otherwise count against the customer's counter. The cap for accepting the data processing agreement (
POST /api/v1/privacy/dpa) still counts.
POST /api/v1/impersonation/stop
Ends the impersonation: the impersonation token is deleted, the end is normally logged and the response is {"ok": true}. If the operator and the customer end the impersonation at the same time, only the call that actually deleted the token writes the end, so it is not logged twice. Called with an ordinary session token, the route answers 400 with no_impersonation. It also works when the account has since become inactive. It sits outside /api/v1/admin/ because that branch is locked during an impersonation.
- Authentication
- Impersonation token
If someone has lost the only device for two-factor authentication, the operator can reset it. In the application, under Operations on a tenant's page, the number next to Users is a toggle that expands the people of the tenant with Name, Email address, Role, Two-factor authentication and Action. Counted and listed are everyone who belongs to the tenant, even if they are currently working in another tenant; Role is their role in this tenant. For customer accounts with two-factor authentication set up, the row offers Reset two-factor authentication; operator accounts show Operator account instead. The button opens the question Remove this person's authenticator code and security keys and end all of their sessions? with the field Reason (recorded in the operator log) (10 to 500 characters) and the buttons Reset and Cancel. Through the API the call is POST /api/v1/admin/users/{id}/second-factor/reset with reason. The authenticator code and the security keys are removed, all of the person's sessions end, and the response carries ok, sessions_ended and mail_queued. If mail_queued is false, the security notice to the account's email address could not be queued; the application then asks you to notify the person yourself. An operator account gives 403 with operator_account, a person without two-factor authentication 409 with no_second_factor, an unknown ID 404 with unknown. If the entry in the operator log or in the account's activity log cannot be written, the reset does not happen and the response is 500 with not_logged.
Frequent questions
Can I use an API key on the application routes?
No. An API key only opens /api/v1/integration/. The other routes that need credentials accept the session token in the Authorization header; a key sent there gets 401.
Why does the chat window answer 404 with unknown_widget?
One of four cases applies: the key is unknown or has been replaced, Enable chat window is off, the assistant is not active, or the account is not active. The answer does not tell these apart; check them one by one on the assistant.
How long is a session token valid?
30 days from sign-in, the same for a token from an invitation, 4 hours for an impersonation token. POST /api/v1/logout deletes it immediately. If the account is not active, requests with it fail with 403 even though it has not expired; signing out with it still works.
Can I change a key's scopes later?
No, there is no route for that. Create a new key with the scopes you need and revoke the old one with Revoke.
What happens to tokens and keys when the account is paused?
They remain, but are rejected: session tokens with 403 and tenant_paused, API keys with 403 and tenant_suspended, the chat window's public key with 404 and unknown_widget. Once the account is active again they work again, unless they have expired in the meantime.
How can I tell that the operator has taken over my account?
Your session list under Account in Signed-in devices then shows an entry Takeover by the operator. It disappears when the takeover is ended or when it expires after 4 hours. You can end the takeover yourself with End in that row.
Ask it yourself, Assistant of this website
Automated assistant