Skip to content
XICBOT

Settings

Deutsch
Documentation · API reference

Integration endpoints

Using an API key: read conversations, enquiries, appointments and metrics, create enquiries and sources, stream chat replies as server-sent events.

In plain language

These endpoints connect your own systems to the service. You need a key for this. With it you read chats, requests, appointments and figures. You can also create requests and knowledge sources. You can ask an assistant questions. The answer then comes back piece by piece. Each endpoint checks whether the key may use it.

Last updated

All documentation topics

The integration API connects your own systems to XICBOT. With an API key you read conversations, enquiries, appointments and metrics, create enquiries and knowledge sources, and put questions to an assistant, with the reply streamed back as server-sent events. Every endpoint lives under /api/v1/integration/ and checks the key's scope on its own.

Overview

The base address is https://app.xicbot.de/api/v1/integration/. Each request carries the key in the X-Api-Key header; keys start with xbot_. Creating a key, choosing its scopes and letting it expire is covered in Keys and webhooks, the authentication scheme in Authentication. These endpoints sit outside the app's session login, so a session token does not open them.

EndpointScopeLabel in the app
GET /statusnone–
GET /conversations, GET /conversations/{id}conversationsRead conversations
GET /leadsleadsRead enquiries
POST /leadsleads.writeCreate enquiries
GET /appointmentsappointmentsRead appointments
GET /analyticsanalyticsRead analytics
POST /knowledge/sourcesknowledge.writeCreate knowledge sources
POST /chatchatUse chat

Writing is a scope of its own: a key with leads reads enquiries but cannot create any. Under Scopes the app puts it this way: *Writing is a separate scope. Without it a key only reads.*

Errors every endpoint shares

Before an endpoint does its actual work, the API checks the key, the account, the plan, the rate caps and the scope. Errors come back as JSON with an error field.

StatuserrorMeaning
400key_in_urlThe key was sent as api_key or key in the query string. The request is turned away before the key is even looked up; the note field says where it belongs.
401key_missingThe X-Api-Key header is missing, empty or does not start with xbot_.
401invalid_keyUnknown, revoked or expired key. All three cases get the same answer.
403tenant_suspendedThe key's account is not active.
402plan_feature_missingThe account's plan does not include the feature for keys and webhooks, for example after a plan change. feature, plan, plan_name and available_in name the feature, the plan and the plans that include it. The key stays valid and works again once the plan includes the feature; the request does not count towards any cap.
429too_many_requestsThe key's per-minute or daily cap has been reached, see Rate limits.
403scope_missingThe key lacks the scope this endpoint requires, such as leads or chat. The required field names it.
403read_onlyThe endpoint writes, and the key lacks the write scope (leads.write or knowledge.write). The required field names it.
422–An input failed field validation. The response carries message and, under errors, the problems per field.
500–Server error. message then reads Serverfehler; class name, file, line and stack trace are left out of the response.

Rate limits

Every key has a per-minute cap, which you enter under Requests per minute when creating it, and a daily cap from your account settings; both are counted per key. Once either is reached, the endpoint answers 429 with too_many_requests. Values, time windows and the response fields are in API limits.

Paging

The lists of conversations, enquiries and appointments return 50 entries per page. Conversations and enquiries are ordered by creation time, newest first; appointments by start time, latest first. Pick the page with page, counting from 1; a missing or smaller value counts as 1, and a value that is not a whole number is rejected with 422. The response echoes the page in page and gives no total. Keep reading until a page holds fewer than 50 entries.

Status

GET /api/v1/integration/status

Tells you what the key may do and reads no data. Use it to test a connection without needing any scope.

Authentication
X-Api-Key header, no scope
Rate limit
The key's per-minute and daily caps, see Rate limits
FieldContent
alivetrue once the request has passed the checks
tenantYour account ID
scopesThe key's scopes as a list
limit_per_minThe key's per-minute cap

Shell command bash

curl -s https://app.xicbot.de/api/v1/integration/status \
  -H "X-Api-Key: $XICBOT_KEY"

# {"alive":true,"tenant":"…","scopes":["leads","leads.write"],"limit_per_min":120}

Conversations

GET /api/v1/integration/conversations

Lists the conversations in your account across all channels, 50 per page, newest first. What a conversation looks like in the app is described in Conversations.

Authentication
X-Api-Key header, scope conversations
Rate limit
The key's per-minute and daily caps, see Rate limits
ParameterRequiredContent
pagenoWhole number, page from 1, default 1
Field in conversations[]Content
idConversation ID
assistant_idAssistant ID
channelwidget (chat window on a website), api (started via POST /chat) or test (test in the app)
titleBuilt from the first question, at most 120 characters, ending in … when shortened
statusopen, closed or escalated
message_countNumber of messages
createdStart time as an ISO 8601 timestamp

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

Returns one conversation with its history: up to 200 messages in chronological order. An ID from another account finds nothing and gets the same answer as an unknown one.

Authentication
X-Api-Key header, scope conversations
Rate limit
The key's per-minute and daily caps, see Rate limits
FieldContent
conversationThe same fields as in the list
messages[].roleuser (question from a visitor or caller), assistant (the assistant's reply) or operator (a message from a person on your team)
messages[].textMessage text in plain text
messages[].timeTimestamp in ISO 8601
StatuserrorMeaning
404not_foundNo conversation with this ID in your account.

Enquiries

Enquiries are contact requests. They arise in a conversation with an assistant or through this API. When an assistant creates an enquiry through a tool, that also fires the lead.created webhook, with a different payload from the one here, see Webhooks triggered. In the app you handle enquiries under Appointments and cases.

GET /api/v1/integration/leads

Lists the enquiries in your account, 50 per page, newest first. Choose the page with page.

Authentication
X-Api-Key header, scope leads
Rate limit
The key's per-minute and daily caps, see Rate limits
Field in leads[]Content
idEnquiry ID
name, email, phoneContact details, each of which may be empty
subjectWhat the enquiry is about
sourceOrigin; enquiries created through the API carry api unless you send something else
statusnew (new), seen (seen) or done (done)
createdCreation time in ISO 8601

POST /api/v1/integration/leads

Creates an enquiry. It starts with status new and fires the lead.created webhook.

Authentication
X-Api-Key header, scope leads.write
Rate limit
The key's per-minute and daily caps, see Rate limits
FieldRequiredRule
subjectyesText, at most 1000 characters
namenoText, at most 120 characters
emailnoValid email address, at most 190 characters
phonenoText, at most 60 characters
sourcenoText, at most 60 characters; defaults to api
external_idnoYour own ID, at most 100 characters; prevents duplicates
StatusResponseMeaning
201lead, duplicate_prevented: falseEnquiry created, webhook fired.
200lead, duplicate_prevented: trueAn enquiry with the same external_id already existed. You get that one back; no second one is created and no webhook fires.
422message, errorssubject is missing or a field rule is broken.

Appointments

GET /api/v1/integration/appointments

Lists the appointments in your account, 50 per page, ordered by start time, latest first. How appointments come about is described in Appointments and cases.

Authentication
X-Api-Key header, scope appointments
Rate limit
The key's per-minute and daily caps, see Rate limits
Field in appointments[]Content
idAppointment ID
typeName of the appointment type
starts_atStart time in ISO 8601
duration_minDuration in minutes
name, emailContact details
statusrequested (requested), confirmed (confirmed) or cancelled (cancelled)

Metrics

GET /api/v1/integration/analytics

Returns the metrics for a period across all assistants combined. Conversations from the test dialog and check conversations are not counted, as in analytics. What each metric means is explained in Analytics.

Authentication
X-Api-Key header, scope analytics
Rate limit
The key's per-minute and daily caps, see Rate limits
ParameterRequiredContent
periodno7d, 30d or 90d; defaults to 30d
zonenoIANA time zone such as Europe/Berlin (default) or America/New_York. 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). Upper and lower case do not matter; the zone is calculated and reported under its usual spelling, so Europe/Berlin for europe/berlin. 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 offset such as +01:00 does not count as a zone.

The window includes the current day and ends at the next midnight in the given zone: 7d covers today and the six days before it.

FieldContent
period, zoneAs requested; zone holds the usual spelling, so Europe/Berlin for europe/berlin, and for an older zone name today's name, so Asia/Kolkata for Asia/Calcutta
from, toWindow boundaries as UTC timestamps
daysNumber of days in the window
assistantnull: the figures cover all assistants
cardsThe metrics, one object per key
Field of a metricContent
valueThe value, shares as percentages; null when not measured
measuredtrue when a value exists
reasonWith measured: false, the reason: no_conversation, no_reply, no_search, no_probe, no_enforcement_check, no_tool_call or no_rating
counter, denominatorFor shares, the numerator and the denominator
GroupKeys in cards
Volumeconversations, messages, answered, escalations, abstentions, messages_per_conversation
Conversation outcomeescalation_rate, self_resolved, frustration_rate, unanswered, abandoned, abandon_rate
Answer qualitycitation_rate, coverage, retrieval_mrr, retrieval_hit_rate, enforcement_rate, hard_topic_rate, blocked_rate
Speed and usagereply_ms_p50, reply_ms_p95, units
Toolstool_calls, tool_executed, tool_rejected, tool_errors
Resultsleads, appointments, carts
Visitor ratingsvisitor_ratings, visitor_helpful_rate, visitor_rating_coverage
StatuserrorMeaning
422unknown_timezoneUnknown zone. The API does not silently fall back to the default.
422–period is something other than 7d, 30d or 90d (message, errors).

Creating knowledge sources

POST /api/v1/integration/knowledge/sources

Creates a knowledge source from text or from a website address. Text is ingested right away; an address is fetched by a background job. How sources feed into answers is covered in Knowledge.

Authentication
X-Api-Key header, scope knowledge.write
Rate limit
The key's per-minute and daily caps, see Rate limits
FieldRequiredRule
titleyesText, at most 200 characters
textyes, if url is missingText, at most 400000 characters
urlyes, if text is missingAddress using http or https, at most 500 characters. If both url and text are sent, url wins.
draftnotrue stores the text as a draft that feeds into no answer. Not allowed together with url.
published_atnoPublication date. If it lies in the future, the text only feeds into answers from then on. Not allowed together with url.
StatusResponseMeaning
201source with id, type: text, status, chunksText processed. status is ready or failed. A failed ingest also answers 201: the source exists, only its ingest failed.
202source with id, type: website, status: newAddress accepted; the fetch runs in the background.
422error: url_unreadableThe address cannot be parsed, for example because scheme or host is missing.
422error: scheme_not_allowedA scheme other than http or https.
422error: name_unresolvableThe host name does not resolve.
422error: target_not_allowedThe target is not a public address.
422message, errorsA field rule is broken, for example draft together with url.
402error: plan_limit_reachedA count limit of your plan is reached; limit names it (knowledge_sources, documents or chunks), allowed and used give the figures. Nothing is created. See Plan gate and plan limits.
503error: plan_limit_unmeasuredThe stock could not be counted; nothing is created. Please try again later.

Whether a website fetch succeeded is reported by the knowledge.indexed and knowledge.failed webhooks. On failure, reason holds a named cause such as no_text_extracted, url_unreachable, website_too_large or redirect_off_origin; other causes show up as ingest_failed. The fetch gets up to three attempts, and every failed attempt sends knowledge.failed. You may therefore receive several events for the same source_id, including a knowledge.indexed after a knowledge.failed; the most recent one counts.

Chat

POST /api/v1/integration/chat

Puts a question to an assistant. Without conversation_id a new conversation is started in the api channel. The reply arrives as a server-sent event stream (text/event-stream), produced the same way as in the app.

Authentication
X-Api-Key header, scope chat
Rate limit
The key's caps, plus your account's monthly quota
FieldRequiredRule
assistant_idyesAssistant ID, at most 64 characters. The reply uses its latest version.
conversation_idnoContinue a conversation, at most 64 characters. Only conversations this assistant started through this API.
textyesThe question, 1 to 4000 characters. If your account has a lower maximum length, that one applies.
StatuserrorMeaning
404not_foundUnknown assistant, or the conversation belongs to another assistant or is not in the api channel.
422no_versionThe assistant has no version yet.
409reply_in_progressA reply is already running in this conversation.
413input_too_longThe question exceeds your account's maximum length; limit and length give both numbers.
429quota_exhaustedThe monthly quota is used up. scope is month, along with month, budget, used and retry_after: the seconds until the start of next month, also sent as the Retry-After header.
422–A field rule is broken (message, errors).

The quota is explained in Plans and quotas. A conversation runs at most one reply at a time; if a run breaks off, the conversation is free again after 180 seconds. A conversation marked as done in the app can be continued too, and the question reopens it: if it had been handed over (escalated_at set), it becomes escalated with a new escalated_at and is back in the inbox; otherwise it becomes open; resolved_at is cleared. No event is sent for this, and a rejected question reopens nothing. A new conversation records as its origin the host from the Origin or Referer header if your call sends one; otherwise the origin stays empty.

Event stream

The response has the type text/event-stream; charset=utf-8 and is delivered uncached (Cache-Control: no-cache, no-store). Each event is an event: line with its type and a data: line with a JSON object, followed by a blank line. The ID of a new conversation arrives in the first event, start; keep it for follow-up questions.

Shell command bash

curl -N https://app.xicbot.de/api/v1/integration/chat \
  -H "X-Api-Key: $XICBOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assistant_id": "…", "text": "What are your opening hours?"}'

event: start
data: {"conversation_id":"…","question_id":null}

event: text
data: {"t":"We are open Monday "}

event: text
data: {"t":"to Friday."}

event: end
data: {"state":"done","message_id":"…",…}
EventFieldsMeaning
startconversation_id, question_idStart of the reply; names the conversation.
texttThe next chunk of reply text. Notices added afterwards and the fallback reply also arrive as text.
citationscitations with number, chunk_id, document, heading, excerptThe sources a finished reply points to by their number in square brackets; excerpt holds at most 300 characters of the passage. For a reply from an active question and answer pair, exactly one citation arrives: number 1, chunk_id made of qa: and the pair's ID, the pair's question as document and heading set to null; the reply itself does not name this number.
follow_upsquestions, dropped, checked, reasonSuggested follow-up questions after a finished reply, at most three. With checked: true every suggestion was checked against your knowledge and dropped counts the discarded ones; with checked: false, reason gives the cause, for example no_knowledge.
contact_offerofferedThe assistant offers to take contact details.
handoffhandoff, reasonThe conversation is handed to a person. reason is for example assistant, conversation_limit or enforcement when the check against the assistant's boundaries hands over.
verificationstate, claims, totalResult of checking hard facts against the sources, after a finished reply. state is no_claims, verified, unverified, no_source (there was nothing to check against) or, for an empty reply, skipped; claims lists unsupported passages and total gives their overall number. After a reply from a question and answer pair this event is not sent, because there the text is the maintained source itself.
redactiontext, reason, count, partsThe reply was corrected after the fact; text holds the entire new wording. With reason: claims, placeholders replace unsupported facts, [nicht belegt] when the reply language is German or undetermined and [not backed] otherwise; count gives their number. If a tool card interrupted the reply, parts is added: the same corrected text in the pieces between the cards, which together give exactly text; if it cannot be split that way, parts is missing. With reason: notice, the required notice about a boundary was rewritten, without count.
enforcementstate, topics, handoverResult of checking the reply against the assistant's boundaries, after a finished reply: state is clear or enforced, topics lists the topics touched and handover says whether the conversation is handed over.
toolid, key, kind, state, preview, input, result, report, error, duration_ms, approval_path, needs_confirmation, chain_capped, created_atThe assistant called a tool. needs_confirmation is true when state is proposed.
model_switchfrom, to, reasonSwitch to a different tier during the reply.
fallbackreason, transientThe reply could not be produced; transient says whether the error was temporary. The assistant's fallback reply follows as text.
errorcode, limitA block before the call, for example conversation_limit once the maximum number of questions per conversation is reached; limit gives that number. The operator sets the maximum for each account individually in the Operations area, see Tenants and limits. A notice follows as text, with conversation_limit also a handoff, and finally end with state: blocked.
livelive, sinceA person on your team has taken over the conversation; the assistant does not answer. The stream then ends with end and state: live.
revokedreasonConsent was withdrawn during the reply; reason is consent_revoked. The text produced so far is discarded and not stored.
endsee belowEnd of the stream.
Field in endContent
statedone, aborted, fallback, blocked, revoked or live. With live, only message_id follows, set to null.
message_idID of the stored reply; null if none was stored
verification_state, enforcement_stateOutcome of the two checks, with the values from verification and enforcement; skipped when the check did not run, for example after a block or a fallback reply; verification_state is also skipped for a reply from a question and answer pair
refusedtrue when a finished reply was a refusal
duration_ms, ttft_msReply duration and time to the first text chunk, in milliseconds
tokens_in, tokens_outUnits used for input and output; both 0 for a reply from a question and answer pair, because no model is asked there
throttled, reply_limitWhether the reply ran throttled because of the quota, and the maximum reply length in units
citationsThe sources of the reply

Webhooks triggered

The endpoints of this API fire the following webhooks, provided you have set up a receiver for the event, see Keys and webhooks. All events with every trigger, including those outside the integration, are listed under Events.

EventLabel in the appTriggerPayload
lead.createdEnquiry createdPOST /leads creates a new enquiryid, conversation_id set to null, source
knowledge.indexedKnowledge source indexedText ingested or website fetch succeededsource_id, documents, chunks; for text also reason set to null
knowledge.failedKnowledge source failedIngest failedsource_id, reason; for text also documents and chunks
lead.createdEnquiry createdPOST /chat: the assistant creates an enquiry through a toolid, conversation_id
appointment.requestedAppointment requestedPOST /chat: the assistant creates an appointment through a toolid, conversation_id
handoff.triggeredHandover triggeredPOST /chat: the reply hands a conversation that has not been handed over yet to a personconversation_id, reason, queue (null if no routing rule applied)
gap.detectedKnowledge gap detectedPOST /chat: a finished reply cites no source and the assistant made no tool call while producing it, even if the search found passages in your knowledge; replies from a question-and-answer pair, replies with a tool call (even one that was only proposed), questions under 12 characters and accounts without knowledge report nothingconversation_id, assistant_id, question (at most 300 characters), kind (refused or uncited)
budget.exhaustedBudget exhaustedPOST /chat: the usage of a reply reaches the monthly quota for the first timemonth, budget, used

Frequent questions

Why does the API answer 401 when the key looks right?

Check that the key is in the X-Api-Key header and starts with xbot_. A revoked or expired key answers invalid_key, exactly like an unknown one. A key in the query string gets 400 with key_in_url.

How do I avoid duplicate enquiries when my system retries?

Send your own ID in external_id with POST /leads. A retry with the same ID answers 200 with duplicate_prevented: true and returns the existing enquiry.

Can I call the API straight from the browser?

Not from another domain: the integration endpoints send no CORS headers. Call them from your backend, which is also where the key stays.

How do I continue a conversation through the API?

Take conversation_id from the start event of the first reply and send it with the next question to POST /chat. Only conversations of the same assistant in the api channel can be continued; while a reply is still running you get 409.

How do I find out whether a website was ingested as a knowledge source?

POST /knowledge/sources with url answers straight away with 202 and status new. The outcome of the fetch arrives through the knowledge.indexed or knowledge.failed webhook, with the source ID in source_id.

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.