Skip to content
XICBOT

Settings

Deutsch
Documentation · API reference

API basics

Base address and version of the XICBOT API, access zones, error formats, response headers, HTTPS, CORS, pagination and safely repeating requests.

In plain language

The API is the way other programs talk to the service. This page applies to all of its endpoints. It gives the base address and the version. It shows how a request is built. It also shows what an error looks like. It adds rules for secure connections. Long lists come in parts. The page says what happens when a request is sent again.

Last updated

All documentation topics

This page covers what applies to every route of the XICBOT API: base address and version, access zones, how requests and errors are structured, HTTPS, response headers, CORS, pagination and how repeated requests behave. Individual routes are documented on the pages for authentication, integration and the other areas.

Base address and version

The API is served under https://app.xicbot.de/api/. Every functional route carries the version v1 in its path, so the base address for your own calls is https://app.xicbot.de/api/v1/. The web app and the clients in the tool kit call the same routes.

To check whether the API is working, call GET /api/v1/public/status; no sign-in is needed. It reports only whether each part is working and gives no further detail. See availability and monitoring.

The simplest way to test a connection is the integration status route. It is the only integration route that needs no scope, and it reads no data:

Shell command bash

curl https://app.xicbot.de/api/v1/integration/status \
  -H 'Accept: application/json' \
  -H 'X-Api-Key: xbot_…'

The response names the account, the scopes and the per-minute cap of the key. Its fields are covered in Integration endpoints, the caps in API limits.

Access zones

Routes under /api/v1/ are grouped into zones, each with its own form of authentication. Within a zone, individual routes may additionally require a role, a plan feature or a key scope.

ZonePaths under /api/v1/AuthenticationFurther requirementsDocumentation
Sign-inregister, verify, login, password/forgot, password/resetnone-Authentication
Publicpublic/status, public/changelog, public/accessibility, public/enquiry, public/demo, public/demo/{id}, invitation/{token}, invitation/accept, cart/pickup/{token}none-Public endpoints
Widgetpublic/widget/{key}/…widget key in the path-Widget endpoints
Integrationintegration/…header X-Api-Keyplan feature; plus a key scope per route, such as conversations or leads.writeIntegration
Accountme, logout, password, totp, webauthn, sessions, logins, confirmation/resend, tenant/switchAuthorization: Beareractive account, except for tenant/switch and logoutAccount
Keys and webhookskeys, webhooksAuthorization: Beareractive account, admin role; plan feature for creating, changing and testingKeys and webhooks
Business areasknowledge, assistants, conversations, catalog, records/{type} and moreAuthorization: Beareractive account, confirmed email address, role or plan feature depending on the routeKnowledge and assistants, Conversations and management
ActivityactivityAuthorization: Beareractive account, confirmed email address, admin roleTeam and roles

Requests

  • Send request bodies as JSON with Content-Type: application/json and ask for JSON with Accept: application/json. The web app does the same.
  • Files, for example for a knowledge source of type file, are sent as multipart/form-data. Let your HTTP client set the Content-Type header including the boundary; a header set by hand without the boundary makes the body unreadable.
  • Under /api/, errors come back as JSON even without an Accept header, not as an HTML page.
  • App routes authenticate with Authorization: Bearer <token>, the integration with the X-Api-Key header. If a key appears in the URL (?api_key= or ?key=), the integration rejects the request with 400 and key_in_url. See authentication for details.
  • Timestamps are returned as ISO 8601 strings, for example in the created field.
  • Assistant replies, for example to POST integration/chat, arrive as a stream with Content-Type: text/event-stream. Errors detected before the stream starts come back as JSON with a matching status. Once the stream is running, an error arrives as an event in the stream and the status no longer changes. The Python client in the tool kit requests these responses with Accept: text/event-stream.
  • The web server aborts a request if the application sends no data for 300 seconds. Set your client timeout accordingly if you expect long responses.

Error format

Errors are returned as JSON with a matching HTTP status. Depending on where the error originates, the body takes one of five forms:

FormWhenExample
error with a code, sometimes with further fieldserrors the API detects itself: key, role, plan, unknown route{"error":"role_too_low","required":"admin"}
message and errorsinput validation, status 422; errors lists the messages for each field{"message":"…","errors":{"email":["…"]}}
error: "validation" and fieldsadditional checks after validation, for example an email address already taken at registration{"error":"validation","fields":{"email":["validation.unique"]}}
only messagefor example a missing or invalid session token (401) and the general rate limit (429){"message":"…"}
message: "Serverfehler"unexpected error with status 500 or above{"message":"Serverfehler"}

For an unexpected error with status 500 or above, the API replaces the text in message with Serverfehler and removes the fields exception, file, line and trace. A response without message does not get one. Messages with a status from 400 to 499 are passed through unchanged, including field errors.

Status codes and error codes

StatusCodeMeaning
400key_in_urlIntegration key sent in the URL instead of the header; note says where it belongs.
401key_missingThe X-Api-Key header is missing or does not start with xbot_.
401invalid_keyKey unknown, revoked or expired; all three cases return the same response.
401only messageSession token missing or no longer valid.
402plan_feature_missingThe plan does not include the feature. Comes with feature, plan, plan_name and available_in, listing the plans that do. See plans and quotas.
402plan_limit_reachedA quantity limit of the plan has been reached. Comes with limit, allowed, used, plan and plan_name.
403role_too_lowInsufficient role; required names the role needed. See team and roles.
403email_unverifiedThe email address has not been confirmed yet.
403no_tenant, tenant_…No account selected, or the account is not active. For an inactive account the code contains its status, and message reads Das Konto ist derzeit nicht aktiv.
403tenant_suspendedThe account behind the integration key is not active.
403scope_missing, read_onlyThe key lacks the required scope; read_only if a write scope is missing. required names the scope.
403https_requiredA request over http with a method other than GET or HEAD that reaches the application unencrypted; see HTTPS.
404route_unknownUnknown route.
404not_foundRecord does not exist. IDs from another account also return 404.
404unknown_widget, demo_expiredUnknown widget key or expired demo.
404unknown_cartUnknown cart pickup token.
405method_not_allowedKnown route, wrong method; allowed lists the permitted methods.
409run_in_progressA run is already open, for example when starting a setup; run describes it.
409reply_in_progressA reply to this conversation is already running via POST integration/chat.
410cart_expiredThe pickup period of the cart has expired.
413input_too_longThe chat input is longer than allowed; limit gives the maximum length, length the length sent.
422validation or errorsInvalid input.
422bad_credentials, invalid_tokenWrong sign-in credentials or invalid confirmation link.
422no_versionThe assistant has no version yet that it could answer with.
429too_many_requests, quota_exhausted, too_many_attempts or only messageCap reached. The waiting time in seconds is in the Retry-After header; sign-in and password reset return it in the retry_in field instead. The integration adds scope (minute or day), limit, retry_after, limit_per_min and wait. quota_exhausted reports an exhausted monthly quota for chat via the integration, with scope: "month", budget, used and retry_after. See limits.
500message: "Serverfehler", plan_gate_misconfiguredUnexpected server-side error; plan_gate_misconfigured reports a misconfigured plan check.
500not_loggedThe action could not be logged and was therefore not carried out. On every path the body is just {"error":"not_logged"}, without message. The application reports “Not saved: the entry in the audit log could not be written. Please try again.”
503plan_limit_unmeasuredThe usage of a plan limit could not be determined; reason gives the cause. The request can be retried later.

Unknown routes and wrong methods

A call for which no route exists is answered by a catch-all branch with JSON instead of an HTML error page. It covers GET, HEAD, POST, PUT, PATCH and DELETE under /api/, including paths outside /api/v1/: /api/ itself and any made-up address return 404 with route_unknown.

If the path exists but not for the method used, the API responds with 405 and method_not_allowed. The allowed field lists the permitted methods in alphabetical order, without HEAD. The catch-all branch does not answer OPTIONS, so preflight requests from other origins stay with the CORS layer.

Shell command bash

curl -s https://app.xicbot.de/api/v1/login -H 'Accept: application/json'
{"error":"method_not_allowed","allowed":["POST"]}

curl -s https://app.xicbot.de/api/v1/beispiel -H 'Accept: application/json'
{"error":"route_unknown"}

HTTPS and HSTS

The API runs over HTTPS. The web server redirects http requests under /api/ to the https address with 301, whatever the method. If an unencrypted request does reach the application, it redirects only GET and HEAD with 301 and rejects every other method with 403 and https_required. The application builds the redirect target from its own address, not from the Host header of the request.

Responses over https carry Strict-Transport-Security: max-age=31536000; includeSubDomains, which tells browsers to use https only for this address for one year. preload is deliberately not set.

Response headers

HeaderValueMeaning
X-Konfigurationsstand12 charactersFingerprint of the effective configuration: the first 12 characters of a SHA-256 hash, no content. Captured when the application starts and also set on error responses. A new value shows that a different version is answering.
X-Conf-StandidentifierVersion of the web server settings for /api/. Shows from outside which version of these settings is in place.
Strict-Transport-Securitymax-age=31536000; includeSubDomainsOver https only; see HTTPS.
X-Content-Type-OptionsnosniffBrowsers use the declared content type as is.
X-Frame-OptionsDENYResponses cannot be embedded in frames.
Referrer-Policyno-referrerBrowsers send no referring address when following links.
X-Robots-Tagnoindex, nofollowAsks search engines not to index the responses.
Retry-AftersecondsOn status 429 from the integration, the monthly quota and the general rate limit. Sign-in and password reset give the waiting time in the retry_in field.
Content-Type, Cache-Control, X-Accel-Bufferingtext/event-stream; charset=utf-8, no-cache, no-store, noOn streamed responses, regardless of the Accept header of the request.
x-rows, x-masked, x-cappednumber, 0 or 1On CSV exports of conversations: row count and whether content is masked; x-capped only on the export of a single conversation and shows whether it was truncated. See conversations and management.
x-sections, x-failed, x-unexported, x-truncatednumberOn the account data export: number of sections, of failed sections, of tables not covered and of shortened tables. See account.

CORS

The API sends CORS headers only on the public section /api/v1/public/*, for example for the status page, the changelog and the widget branch, and only for the approved origins listed in the table. The integration and all authenticated routes send no CORS headers. Call the integration from your server, not from JavaScript in the browser.

GET cart/pickup/{token} follows its own rule. The route is meant for server-to-server calls. In a browser, only the origin of the stored handover address may read the response. Responses carry Cache-Control: no-store and Vary: Origin.

SettingValue
Pathsapi/v1/public/*
Originsnull for the widget, which runs in a sandboxed iframe without allow-same-origin and sends Origin: null; the own origins of app and widget; further individually approved origins including the scheme. Entries containing * are discarded.
MethodsGET, POST, DELETE, OPTIONS
Request headersContent-Type, Accept
Headers readable by scriptsRetry-After
Preflight cache lifetime600 seconds (max_age)
Credentialsnot supported (supports_credentials: false)

Because only Content-Type and Accept are allowed, a preflight from another origin permits neither X-Api-Key nor Authorization. The routes for the chat window are covered in widget endpoints, the remaining public routes in public endpoints.

Pagination

Lists are paginated with the query parameter page, starting at 1. Page size, permitted ranges and response fields differ by route. Where a route checks a range, values outside it return 422; the integration treats values below 1 as 1.

Routepageper_pageResponse fields
GET integration/conversations, integration/leads, integration/appointmentsfrom 1; lower values count as 1fixed at 50list and page
GET conversations1 to 100001 to 200, default 50conversations, total, page, per_page
GET catalogfrom 11 to 200, default 50products, total, page, per_page; total counts all products, even when search is set
GET records/{type}from 11 to 200, default 50entries, total, page, per_page; total respects the status filter
GET activity1 to 1000fixed at 25entries, actions, total, page, pages; actions lists the action types present, for filtering

Repeated requests

The API prevents duplicates through IDs in the request body or through the state of the record, not through a dedicated header. Whether a request can be safely repeated after a timeout depends on the route:

RouteBehaviour on repetition
POST integration/leads with external_idThe ID may be up to 100 characters. If a lead with this ID already exists, the route returns 200, the existing entry and duplicate_prevented: true; a new lead returns 201 and duplicate_prevented: false.
POST integration/leads without external_idEvery call creates a new lead.
POST catalog/importMatches products by external_id. Importing the same file a second time creates no duplicates. Up to 500 products per call; the response contains new, changed and total. See catalog and shop.
Confirm, reject or report a proposal (…/tool/{call}/confirm, …/reject, …/report)A proposal that has already been decided is not executed again; the response carries duplicate: true. A second report on an executed proposal in the widget also returns duplicate: true.
DELETE keys/{id}Revoking a second time changes nothing; withdrawn keeps the time of the first revocation.
POST public/demo200 for an existing demo, 201 for a new one.
POST integration/knowledge/sourcesEvery call creates a new source.
POST integration/chatWithout conversation_id, every call starts a new conversation. With conversation_id you continue a conversation that the same assistant started via the integration; any other ID returns 404. While a reply to the conversation is still running, another call returns 409 with reply_in_progress. After 180 seconds a running reply is treated as aborted and the conversation is free again.

Frequent questions

How can I tell whether an error is caused by my request?

A status from 400 to 499 concerns your request: the code is in error, field errors are under errors or fields. From 500 upwards the error is on the server side; for an unexpected error the message is simply Serverfehler. A 503 with plan_limit_unmeasured can be retried later.

Can I call the integration directly from the browser?

No. The integration sends no CORS headers, and the key would be exposed in the browser. Call /api/v1/integration/… from your server. The chat window has its own widget branch under /api/v1/public/widget/{key}/.

What happens to a request sent over http?

The web server redirects it to https with 301. If it reaches the application unencrypted, the application rejects anything other than GET and HEAD with 403 and https_required. Either way, its content has already travelled unencrypted. Configure the base address with https:// and revoke any key sent this way.

Why does a route return 405 rather than 404?

The path exists, but not for the method you used. The allowed field lists the permitted methods, for example ["POST"] for /api/v1/login. A 404 with route_unknown, by contrast, means that the path does not exist.

How do I avoid duplicate leads when I retry a call?

Include a stable external_id with POST /api/v1/integration/leads. A retry then returns 200 with the existing entry and duplicate_prevented: true instead of creating a second lead.

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.