On this page
API
Everything you do in the editor runs through an open interface — the same one you can address yourself. This chapter explains the base address, signing in and the error format, and then lists every endpoint that is open to your account.
What the API is for
The interface is the control plane of XICflow: websites, pages, blocks, media, AI runs, domains, requests and billing.
The editor works exclusively through this interface. Whatever you do there with the mouse you can therefore also do from a program of your own — for instance take content over from another system, collect requests or trigger a publish.
| Base address | https://api.xicflow.com/api |
|---|---|
| Format | JSON in UTF-8, including for errors. The exceptions are the image upload (multipart/form-data), the CSV export, the invoice download as PDF and the two chat endpoints, which return an event stream (text/event-stream). |
| Version | The specification carries version 1.0.0. The address itself contains no version number; the machine-readable version is always authoritative. |
| Scope | 115 operations across 92 paths, grouped into 19 categories. |
| Account separation | Every resource belongs to exactly one account. The API answers an ID from another account with 404. |
| Roles | Reading is open to every member. Content changes require at least “Editor”, creating and deleting websites and domains at least “Administrator”, and billing actions the “Owner” role. |
Browser calls are limited to two origins
For requests coming from a web page, the interface only allows xicflow.com and www.xicflow.com. The public endpoints for the contact form and the site assistant are exempt, because published websites run under their own domain. A program on your own server is not affected by this limit.
Signing in and sending the token
A token is created when you sign in and is then sent with every request in the “Authorization” header.
-
Call POST /auth/login with your email address and password.
To create a new account, use POST /auth/register instead; that call already returns a token as well.
-
Take the “token” field from the response.
The “access_token” field holds the same value and exists only for compatibility.
-
Send the header “Authorization: Bearer <token>” with every further request.
-
End the session via POST /auth/logout once you no longer need the token.
A token stays valid until it is revoked — individually via /auth/logout or DELETE /auth/sessions/{id}, or all the others at once via DELETE /auth/sessions.
curl -X POST https://api.xicflow.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"<E-Mail>","password":"<Passwort>"}'{
"token": "<token>",
"access_token": "<token>",
"token_type": "bearer",
"user": {
"id": "<uuid>",
"email": "<E-Mail>",
"name": "<Name>",
"site_role": "owner",
"tenant_id": "<uuid>",
"tenant": {
"id": "<uuid>",
"name": "<Kontoname>",
"slug": "<slug>",
"plan": "<Tarif>",
"status": "active",
"trial_ends_at": "<Zeitpunkt>"
}
}
}With the token in hand, every other endpoint is within reach:
curl https://api.xicflow.com/api/sites \
-H "Authorization: Bearer <token>"If two-factor sign-in is active for the account and the code is missing, /auth/login does not return a token but “totp_required” together with a short-lived “challenge_token” (valid for ten minutes). You either redeem it via POST /auth/totp/challenge or repeat the login with the “totp_code” field. A backup code is accepted in the same place.
A few endpoints work without a token — sign-in and registration, the return leg of Google sign-in, passkey sign-in, redeeming the two-factor challenge, the contact form of published websites, the embedded site assistant, previewing and accepting a team invitation, and the Stripe callback. In the reference further down they carry the label “no token”.
Conventions and error format
Errors come back as JSON. Depending on the cause they carry an “error” field with a fixed key, or a list of fields under “errors”.
Invalid input (422)
If a request violates the field rules, the response names every affected field individually.
{
"message": "<Meldung>",
"errors": {
"<Feldname>": ["<Meldung zum Feld>"]
}
}Role is not sufficient (403)
“required” names the role that is needed, “role” the one you actually have.
{
"error": "forbidden",
"message": "Ihre Rolle (…) erlaubt diese Aktion nicht.",
"required": "editor",
"role": "viewer"
}Quota used up (402)
The “resource” field names the quota concerned. For the editor chat the key reads “chat_tokens_exhausted” instead.
{
"error": "quota_exceeded",
"resource": "ai_image",
"plan": "<Tarif>",
"remaining": 0,
"upgrade": true,
"message": "Dein monatliches Kontingent für KI-Bilder ist erschöpft. …"
}Account suspended (403)
If the account is suspended because of unpaid invoices, every endpoint that needs a token answers like this, no matter what was requested.
{
"error": "account_suspended",
"message": "Dein Konto ist wegen offener Rechnungen gesperrt. …"
}Status codes in use
| Code | Meaning |
|---|---|
| 200 | Success. |
| 201 | Created. |
| 202 | Accepted and processed in the background — progress runs through GET /ai/generations/{generation}. |
| 302 | Redirect; occurs only on the two Google sign-in endpoints, which are browser navigations. |
| 400 | Invalid request. |
| 401 | Missing or invalid token, wrong credentials or wrong two-factor code. |
| 402 | Quota used up — upgrade the plan or top up. |
| 403 | No access: role is insufficient or the account is suspended. |
| 404 | Does not exist — or belongs to another account. |
| 409 | Conflict: a precondition is not met. |
| 410 | No longer valid, for example an invitation that was already redeemed or has expired. |
| 422 | Field rules violated. |
| 429 | Too many requests in a short time. |
| 500 | Server error. |
| 503 | Temporarily unavailable. |
Per-minute limits
Sensitive and expensive endpoints are throttled. Once the limit is reached, the API answers with 429.
Counting happens per started minute. On endpoints without a token the sending address is counted, on all others the signed-in account. There is no blanket limit across the whole interface — only the endpoints listed here are capped.
| Endpoints | Per minute |
|---|---|
| Change password, enable or disable two-factor, regenerate backup codes, data export | 6 |
| Contact form of published websites, sending a team invitation | 10 |
| Sign-in, registration, two-factor challenge, passkey sign-in, return leg of Google sign-in, changing the display name, signing out all other sessions, previewing and accepting an invitation | 12 |
| Signing out a single session, generating alt text with AI, chat of the public site assistant | 20 |
| All AI endpoints, blog generation, publishing, image upload, verifying and binding a domain, rebuilding the knowledge base, setting the interface language, all commercial actions | 30 |
| Configuration of the public site assistant | 60 |
| Polling the status of a generation | 120 |
Long runs and retries
Building and publishing take too long for a single response. These endpoints acknowledge immediately and carry on in the background.
-
The call answers with 202 and a “generation_id”.
This applies to POST /ai/build, POST /sites/{site}/publish and POST /sites/{site}/blog/generate.
-
Poll GET /ai/generations/{generation} until “status” is no longer queued or running.
This endpoint is generously capped at 120 requests per minute, so polling once a second is expected.
-
On “succeeded” the result sits in the “output” field, on “failed” the cause sits in “error”.
A failed run is not repeated automatically
Building and publishing deliberately run exactly once, because a second attempt would not produce the same result. If it says “failed”, start the process again yourself. The interface has no idempotency keys either: sending the same creating request twice produces a second record.
Two endpoints do not answer with a finished document but with a running event stream: the editor assistant at POST /ai/chat and the assistant of published websites at POST /public/assistant/{siteSlug}/chat. Both send events as text/event-stream, starting with “start” and ending with “done” or “error”.
Reference of all endpoints
Every entry can be expanded and then shows purpose, parameters, the shape of the request and the possible responses.
This list is generated, not transcribed
It is produced when the page is built, from the same specification the interface itself serves. When an endpoint is added it appears here after the next build — in exchange, short texts, field notes and the endpoint-specific response descriptions stay German in the English version; only the recurring standard responses are translated. Operator endpoints are not included.
Auth
Registrierung, Login, Sitzungen, 2FA/TOTP, Passkeys, OAuth.
POST /auth/register Konto registrieren no token
Legt Nutzer + eigenen Tenant (Owner) an und liefert ein Bearer-Token. 14-Tage-Trial.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| Body | string (email) | yes | E-Mail (wird normalisiert, +alias entfernt). | |
| password | Body | string | yes | Mindestens 10 Zeichen. |
| name | Body | string | yes | Anzeigename. |
Request · application/json
{
"email": "<email>",
"password": "<string>",
"name": "<string>"
}Responses
200Token + Nutzerprofil ({token, access_token, token_type, user}).422E-Mail bereits vergeben oder ungültige Eingabe.
POST /auth/login Login (Passwort, optional 2FA) no token
Liefert {token, user}. Bei aktivem TOTP ohne totp_code stattdessen {totp_required:true, challenge_token, expires_in}.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| Body | string (email) | yes | ||
| password | Body | string | yes | |
| totp_code | Body | string | no | Optionaler 6-stelliger TOTP- oder Backup-Code. |
Request · application/json
{
"email": "<email>",
"password": "<string>",
"totp_code": "<string>"
}Responses
200{token,user} oder {totp_required,challenge_token,expires_in}.401Ungültige Zugangsdaten oder 2FA-Code.403Konto gesperrt.429Zu viele 2FA-Fehlversuche.
POST /auth/totp/challenge 2FA per Challenge-Token einlösen no token
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| challenge_token | Body | string | yes | 48-stelliges Token aus der Login-Antwort. |
| code | Body | string | yes | TOTP- oder Backup-Code. |
Request · application/json
{
"challenge_token": "<string>",
"code": "<string>"
}Responses
200{token,user}.401Ungültig/abgelaufen.429Zu viele Versuche.
POST /auth/passkey/login/options Passkey-Login: Optionen anfordern no token
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| Body | string (email) | no | Optional — schränkt allowCredentials ein. |
Request · application/json
{
"email": "<email>"
}Responses
200WebAuthn-publicKey-Request-Optionen (flach).
POST /auth/passkey/login/verify Passkey-Login: Antwort verifizieren no token
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| id | Body | string | yes | Credential-ID (base64url). |
| response | Body | object | yes | WebAuthn-Assertion: clientDataJSON, authenticatorData, signature. |
Request · application/json
{
"id": "<string>",
"response": {}
}Responses
200{token,user}.401Challenge abgelaufen / Passkey unbekannt.403Konto gesperrt.
GET /auth/oauth/{provider}/redirect Google-SSO starten (302) no token
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| provider | Path | string | yes | OAuth-Provider, aktuell nur "google". |
| return | Query | string | no | Rückkehr-URL nach erfolgreichem Login. |
Responses
302Redirect (browser navigation)404Unbekannter Provider.
GET /auth/oauth/{provider}/callback Google-SSO-Callback (302 mit Token-Fragment) no token
Der State wird gegen das beim Redirect gesetzte Cookie geprüft. Im Fehlerfall ebenfalls 302, aber mit #error=… (state_missing, state_mismatch, no_code, exchange_failed, no_sub, email_unverified, account_disabled).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| provider | Path | string | yes | OAuth-Provider, aktuell nur "google". |
| state | Query | string | no | CSRF-State. |
| code | Query | string | no | Authorization-Code. |
Responses
302Redirect ins Frontend mit #token=…&provider=…(&new=1) im Fragment.
POST /auth/oauth/{provider}/callback SSO-Callback als form_post (302, identisch zu GET) no token
Alias derselben Verarbeitung für Provider mit response_mode=form_post. State und Code werden weiterhin aus der Query gelesen.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| provider | Path | string | yes | OAuth-Provider, aktuell nur "google". |
| state | Query | string | no | CSRF-State. |
| code | Query | string | no | Authorization-Code. |
Responses
302Redirect ins Frontend mit #token=… im Fragment.
GET /me Aktuelles Nutzerprofil
Responses
200Nutzerprofil inkl. tenant (plan/status/trial_ends_at).401Not authenticated (missing or invalid bearer token)
POST /auth/logout Aktuelles Token widerrufen
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)
POST /auth/change-password Passwort ändern
Widerruft alle anderen Sessions. new_password braucht Bestätigung (new_password_confirmation).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| current_password | Body | string | yes | |
| new_password | Body | string | yes | Mindestens 10 Zeichen, confirmed. |
| new_password_confirmation | Body | string | no |
Request · application/json
{
"current_password": "<string>",
"new_password": "<string>",
"new_password_confirmation": "<string>"
}Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)422Aktuelles Passwort falsch.
GET /auth/sessions Aktive Sitzungen auflisten
Alle Bearer-Token des angemeldeten Nutzers, zuletzt genutzte zuerst. Die eigene Sitzung ist mit current:true markiert.
Responses
200{sessions:[{id, device, last_used_at, created_at, current}]}.401Not authenticated (missing or invalid bearer token)
DELETE /auth/sessions Alle anderen Sitzungen abmelden
Widerruft jedes Token außer dem aktuell verwendeten.
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)429Too many requests (rate limit)
DELETE /auth/sessions/{id} Einzelne Sitzung abmelden
Nur eigene Sitzungen; eine fremde ID trifft nichts und wird trotzdem mit 200 quittiert.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| id | Path | string | yes | Sitzungs-ID aus GET /auth/sessions. |
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)429Too many requests (rate limit)
GET /auth/totp/status 2FA-Status
Responses
200{enabled, enabled_at, has_pending_secret, backup_codes_left}.401Not authenticated (missing or invalid bearer token)
POST /auth/totp/setup 2FA einrichten (Pending-Secret)
Responses
200{secret, provisioning_uri} (otpauth://).401Not authenticated (missing or invalid bearer token)422Bereits aktiviert.
POST /auth/totp/enable 2FA aktivieren
Verifiziert den ersten Code und liefert die Backup-Codes EINMALIG im Klartext.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| code | Body | string | yes | Aktueller TOTP-Code. |
Request · application/json
{
"code": "<string>"
}Responses
200{enabled:true, backup_codes[]}.401Not authenticated (missing or invalid bearer token)422Ungültiger Code / kein Pending-Secret.
POST /auth/totp/disable 2FA deaktivieren
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| password | Body | string | yes | |
| code | Body | string | yes | TOTP- oder Backup-Code. |
Request · application/json
{
"password": "<string>",
"code": "<string>"
}Responses
200{enabled:false}.401Not authenticated (missing or invalid bearer token)422Passwort oder Code falsch.
POST /auth/totp/regenerate-codes Backup-Codes neu erzeugen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| password | Body | string | yes | |
| code | Body | string | yes |
Request · application/json
{
"password": "<string>",
"code": "<string>"
}Responses
200{backup_codes[]}.401Not authenticated (missing or invalid bearer token)422Validation error
POST /auth/passkey/register/options Passkey registrieren: Optionen
Responses
200WebAuthn-publicKey-Create-Optionen (flach).401Not authenticated (missing or invalid bearer token)
POST /auth/passkey/register/verify Passkey registrieren: verifizieren
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| id | Body | string | yes | Credential-ID (base64url). |
| response | Body | object | yes | clientDataJSON + attestationObject. |
| name | Body | string | no | Optionaler Anzeigename. |
Request · application/json
{
"id": "<string>",
"response": {},
"name": "<string>"
}Responses
200{ok:true, id, credential_name}.401Not authenticated (missing or invalid bearer token)422Challenge/Attestation ungültig.
GET /auth/passkeys Passkeys auflisten
Responses
200{passkeys:[{id, credential_name, last_used_at, created_at}]}.401Not authenticated (missing or invalid bearer token)
PUT /auth/passkeys/{id} Passkey umbenennen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| id | Path | string | yes | Credential-ID. |
| name | Body | string | yes |
Request · application/json
{
"name": "<string>"
}Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
DELETE /auth/passkeys/{id} Passkey löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| id | Path | string | yes | Credential-ID. |
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
GET /auth/oauth/identities Verknüpfte OAuth-Identitäten
Responses
200{providers:[…]}.401Not authenticated (missing or invalid bearer token)
DELETE /auth/oauth/{provider} OAuth-Identität entkoppeln
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| provider | Path | string | yes | OAuth-Provider, aktuell nur "google". |
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)422Letzter Zugang ohne gesetztes Passwort.
Konto
Eigenes Profil, Oberflächensprache und Datenexport (DSGVO Art. 20).
POST /account/profile Eigenen Anzeigenamen ändern
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| name | Body | string | yes | Anzeigename, max. 190 Zeichen (wird getrimmt). |
Request · application/json
{
"name": "<string>"
}Responses
200{name}.401Not authenticated (missing or invalid bearer token)422Validation error429Too many requests (rate limit)
POST /account/locale Oberflächensprache setzen
Am Konto gespeichert und damit gerätübergreifend wirksam.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| language | Body | string | yes | de oder en. |
Request · application/json
{
"language": "de"
}Responses
200{language}.401Not authenticated (missing or invalid bearer token)422Validation error429Too many requests (rate limit)
GET /account/data-export Eigene Daten exportieren (DSGVO Art. 20)
JSON-Download (Content-Disposition: attachment) mit Konto, Nutzern, Sites samt Seiten/Collections/Einträgen/Domains, Kontaktanfragen, Rechnungen und Medien-Metadaten des eigenen Kontos. Enthält bewusst keine Geheimnisse (Passwort-Hash, TOTP-Secret, Backup-Codes, Stripe-Referenzen). Rate-Limit 6/min.
Responses
200{meta, account, users, sites, contact_submissions, invoices, assets}.401Not authenticated (missing or invalid bearer token)422Kein Konto (Tenant) am Nutzer hinterlegt.429Too many requests (rate limit)
Team
Mitglieder, Rollen, Einladungen und Sitz-Kontingent eines Kontos.
GET /team Mitglieder, Einladungen und Sitze
seats.limit stammt aus dem Tarif; belegte Sitze = aktive Mitglieder plus offene, nicht abgelaufene Einladungen. Bei unbegrenzten Sitzen ist unlimited true und available null.
Responses
200{members:[{id,name,email,role,is_owner,is_you,joined_at}], invitations:[{id,email,role,expires_at,expired}], seats:{used,limit,unlimited,available,plan}, your_role, roles}.401Not authenticated (missing or invalid bearer token)422Kein Konto (Tenant) am Nutzer hinterlegt.
POST /team/invitations Mitglied einladen
Erfordert mindestens die Rolle admin; vergeben werden können nur Rollen unterhalb der eigenen. Eine bereits offene Einladung an dieselbe Adresse wird ersetzt. Die Einladung gilt 14 Tage; der Annahme-Link wird zusätzlich zurückgegeben, damit er auch ohne Mailzustellung weitergegeben werden kann. Rate-Limit 10/min.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| Body | string (email) | yes | Adresse ohne bestehendes XICflow-Konto. · max. 255 Zeichen | |
| role | Body | string | yes | admin|editor|viewer. |
Request · application/json
{
"email": "<email>",
"role": "admin"
}Responses
201{invitation:{id,email,role,expires_at,expired}, accept_url}.401Not authenticated (missing or invalid bearer token)403Rolle reicht nicht (forbidden) oder Zielrolle zu hoch (role_too_high).422already_member, email_taken, seat_limit_reached (mit limit) oder kein Tenant.429Too many requests (rate limit)
DELETE /team/invitations/{invitation} Offene Einladung widerrufen
Erfordert mindestens die Rolle admin.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| invitation | Path | string | yes | Einladungs-UUID aus GET /team. |
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)403Rolle reicht nicht.404Not found (does not exist, or belongs to another account)422Kein Tenant.
POST /team/members/{member}/role Rolle eines Mitglieds ändern
Die eigene Rolle und die des Konto-Eigentümers sind nicht änderbar. Änderbar sind nur Mitglieder, deren aktuelle UND neue Rolle unterhalb der eigenen liegen.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| member | Path | string | yes | Nutzer-UUID aus GET /team. |
| role | Body | string | yes | admin|editor|viewer. |
Request · application/json
{
"role": "admin"
}Responses
200{ok:true, role}.401Not authenticated (missing or invalid bearer token)403Rolle reicht nicht.404Mitglied nicht im eigenen Konto.422cannot_change_owner, cannot_change_self oder kein Tenant.
DELETE /team/members/{member} Mitglied entfernen
Entfernt Zugang und Konto des Mitglieds und widerruft dessen Token. Eigentümer und eigenes Konto sind ausgenommen.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| member | Path | string | yes | Nutzer-UUID aus GET /team. |
Responses
200{ok:true}.401Not authenticated (missing or invalid bearer token)403Rolle reicht nicht.404Mitglied nicht im eigenen Konto.422cannot_remove_owner, cannot_remove_self oder kein Tenant.
GET /public/team/invitation/{token} Einladung vorab ansehen (ohne Anmeldung) no token
Löst den Einmal-Token auf, damit die Annahme-Seite Team und Rolle anzeigen kann. Rate-Limit 12/min.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| token | Path | string | yes | Einmal-Token aus dem Einladungslink. |
Responses
200{email, role, team, expires_at}.404Token unbekannt (invalid).410Bereits angenommen (already_accepted) oder abgelaufen (expired).429Too many requests (rate limit)
POST /public/team/accept Einladung annehmen (ohne Anmeldung) no token
Legt das Konto mit der eingeladenen Rolle an, löst den Token ein und liefert direkt ein Bearer-Token. Die E-Mail stammt aus der Einladung und gilt damit als bestätigt. Rate-Limit 12/min.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| token | Body | string | yes | Einmal-Token aus dem Einladungslink. |
| name | Body | string | yes | Anzeigename, max. 255 Zeichen. |
| password | Body | string | yes | Mindestens 10 Zeichen. |
Request · application/json
{
"token": "<string>",
"name": "<string>",
"password": "<string>"
}Responses
200{token, access_token, token_type, user:{id,name,email,role,tenant_id}}.404Token unbekannt (invalid).409Für diese E-Mail existiert bereits ein Konto (email_taken).410Bereits angenommen oder abgelaufen.422Validation error429Too many requests (rate limit)
Sites
CRUD der Websites eines Kontos plus Veröffentlichung.
GET /sites Sites des Kontos auflisten
Responses
200{data:[Site]} inkl. theme, domains, pages_count.401Not authenticated (missing or invalid bearer token)
POST /sites Site anlegen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| name | Body | string | yes | |
| slug | Body | string | yes | Kleinbuchstaben/Ziffern/Bindestrich, je Konto eindeutig. |
| theme_id | Body | string (uuid) | no | Optionales Theme (UUID, eigen oder System). |
| default_locale | Body | string | no | Default "de". |
| locales | Body | array | no | Aktive Sprachen, z. B. ["de","en"]. |
| company | Body | object | no | Firmenstammdaten. |
| branding | Body | object | no | |
| trust_bar | Body | object | no | |
| xictraq_id | Body | string | no | XICTRAQ-Property für Analytics/Monitoring. |
| contact_endpoint | Body | string | no | |
| contact_recipient | Body | string | no | |
| google_verification | Body | string | no | |
| settings | Body | object | no |
Request · application/json
{
"name": "<string>",
"slug": "<string>",
"theme_id": "<uuid>",
"default_locale": "<string>",
"locales": [],
"company": {},
"branding": {},
"trust_bar": {},
"xictraq_id": "<string>",
"contact_endpoint": "<string>",
"contact_recipient": "<string>",
"google_verification": "<string>",
"settings": {}
}Responses
201{data:Site}.401Not authenticated (missing or invalid bearer token)422Slug vergeben / ungültig.
GET /sites/{site} Site abrufen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:Site} inkl. pages, theme, domains.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
PUT /sites/{site} Site aktualisieren
status und primary_domain_id sind bewusst NICHT hierüber setzbar (Publish- bzw. Domain-Flow).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| name | Body | string | no | |
| slug | Body | string | no | |
| theme_id | Body | string (uuid) | no | |
| default_locale | Body | string | no | |
| locales | Body | array | no | |
| company | Body | object | no | |
| branding | Body | object | no | |
| settings | Body | object | no |
Request · application/json
{
"name": "<string>",
"slug": "<string>",
"theme_id": "<uuid>",
"default_locale": "<string>",
"locales": [],
"company": {},
"branding": {},
"settings": {}
}Responses
200{data:Site}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Slug vergeben.
DELETE /sites/{site} Site löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/publish Site veröffentlichen (asynchron)
Reiht einen Publish-Job ein und antwortet sofort. Status via GET /ai/generations/{generation_id}.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| label | Body | string | no | Optionales Versionslabel. |
| notes | Body | string | no | Optionale Notiz. |
Request · application/json
{
"label": "<string>",
"notes": "<string>"
}Responses
202{generation_id, status:"queued"}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
GET /sites/{site}/regions Seiten-IDs der Header-/Footer-Regionen
Header und Footer sind eigene System-Seiten (type=system) und tauchen daher nicht in GET /sites/{site}/pages auf. Dieser Endpunkt liefert ihre Seiten-IDs als Anker für den blockbasierten Header-/Footer-Editor; fehlende Regionsseiten werden beim ersten Aufruf angelegt.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:{header, footer}} — je Region eine Seiten-UUID.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Seiten & Blöcke
Seiten (bilingual) und ihre Inhaltsblöcke.
GET /sites/{site}/pages Seiten einer Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:[Page]} inkl. translations.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/pages Seite anlegen
Bilingual: translations je Locale ({locale,title,slug,…}).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| slug | Body | string | yes | |
| path | Body | string | yes | Beginnt mit "/", nur [a-z0-9/-]. |
| type | Body | string | no | page|post|landing|legal|system. |
| status | Body | string | no | draft|published|scheduled|archived. |
| template | Body | string | no | |
| parent_id | Body | string (uuid) | no | |
| sort_order | Body | integer | no | |
| is_home | Body | boolean | no | |
| is_noindex | Body | boolean | no | |
| seo | Body | object | no | |
| translations | Body | array | yes | Pflicht, min. 1 Eintrag mit locale/title/slug. |
Request · application/json
{
"slug": "<string>",
"path": "<string>",
"type": "<string>",
"status": "<string>",
"template": "<string>",
"parent_id": "<uuid>",
"sort_order": 0,
"is_home": false,
"is_noindex": false,
"seo": {},
"translations": []
}Responses
201{data:Page}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Pfad vergeben / ungültig.
GET /pages/{page} Seite abrufen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| page | Path | string | yes | Seiten-UUID. |
Responses
200{data:Page} inkl. translations, sections, blocks.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
PUT /pages/{page} Seite aktualisieren
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| page | Path | string | yes | Seiten-UUID. |
| slug | Body | string | no | |
| path | Body | string | no | |
| type | Body | string | no | |
| status | Body | string | no | |
| is_home | Body | boolean | no | |
| is_noindex | Body | boolean | no | |
| seo | Body | object | no | |
| published_at | Body | string (date-time) | no | |
| translations | Body | array | no | Upsert je Locale. |
Request · application/json
{
"slug": "<string>",
"path": "<string>",
"type": "<string>",
"status": "<string>",
"is_home": false,
"is_noindex": false,
"seo": {},
"published_at": "<date-time>",
"translations": []
}Responses
200{data:Page}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
DELETE /pages/{page} Seite löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| page | Path | string | yes | Seiten-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
PUT /pages/{page}/reorder-blocks Blöcke neu sortieren
blocks: Liste von Block-IDs (Position = sort_order) oder Objekten {id,section_id,sort_order} für sektionsübergreifendes Verschieben.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| page | Path | string | yes | Seiten-UUID. |
| blocks | Body | array | yes | IDs oder {id,section_id,sort_order}-Objekte. |
Request · application/json
{
"blocks": []
}Responses
200{data:[Section]} inkl. sortierter Blöcke.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
POST /pages/{page}/blocks Block hinzufügen
props werden gegen block_types.schema validiert (unbekannte Props -> 422).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| page | Path | string | yes | Seiten-UUID. |
| type | Body | string | no | block_types.key (alternativ block_type_id). |
| block_type_id | Body | string (uuid) | no | Alternativ zu type. |
| section_id | Body | string (uuid) | no | Ziel-Section (sonst erste/neue). |
| props | Body | object | no | Block-Eigenschaften gemäß Schema. |
| content | Body | object | no | |
| sort_order | Body | integer | no | |
| is_visible | Body | boolean | no |
Request · application/json
{
"type": "<string>",
"block_type_id": "<uuid>",
"section_id": "<uuid>",
"props": {},
"content": {},
"sort_order": 0,
"is_visible": false
}Responses
201{data:Block}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Unbekannter Typ / Props verletzen das Schema.
PUT /pages/{page}/blocks Alle Blöcke einer Seite ersetzen (Autosave)
Vollständiger Abgleich: Blöcke mit bekannter id werden aktualisiert, Einträge ohne id neu angelegt, alle nicht gesendeten Blöcke der Seite gelöscht. Die Reihenfolge im Array bestimmt die Reihenfolge auf der Seite. Blöcke mit unbekanntem oder deaktiviertem type werden übersprungen. Fehlt content, bleiben vorhandene Übersetzungen erhalten. Maximal 400 Blöcke je Aufruf.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| page | Path | string | yes | Seiten-UUID. |
| blocks | Body | array | yes | Pflicht (darf leer sein — leert dann die Seite). Elemente: {id?, type, props?, content?}; type ist ein block_types.key. |
Request · application/json
{
"blocks": []
}Responses
200{ok:true, blocks:[{id,type}], count}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
PUT /blocks/{block} Block aktualisieren
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| block | Path | string | yes | Block-UUID. |
| props | Body | object | no | |
| content | Body | object | no | |
| block_type_id | Body | string (uuid) | no | Optionaler Typwechsel. |
| type | Body | string | no | |
| section_id | Body | string (uuid) | no | |
| sort_order | Body | integer | no | |
| is_visible | Body | boolean | no |
Request · application/json
{
"props": {},
"content": {},
"block_type_id": "<uuid>",
"type": "<string>",
"section_id": "<uuid>",
"sort_order": 0,
"is_visible": false
}Responses
200{data:Block}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
DELETE /blocks/{block} Block löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| block | Path | string | yes | Block-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Theme
Design-Tokens je Site (Copy-on-Write für System-Themes).
GET /sites/{site}/theme Theme der Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:Theme} mit tokens.401Not authenticated (missing or invalid bearer token)404Kein Theme verfügbar.
PUT /sites/{site}/theme Theme aktualisieren
Copy-on-Write: ein geteiltes System-Theme wird beim ersten Schreiben geklont.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| name | Body | string | no | |
| tokens | Body | object | yes | Design-Tokens (colors, colorsDark, fonts, gradients, hero, sectionStyle, headerStyle …). |
Request · application/json
{
"name": "<string>",
"tokens": {}
}Responses
200{data:Theme}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
Medien
Bild-Uploads je Site.
GET /sites/{site}/media Mediathek einer Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| q | Query | string | no | Volltextsuche über Dateiname, Alt-Text und Titel (case-insensitiv). |
Responses
200{data:[Asset]} (neueste zuerst) mit url, path, alt, title, width, height, bytes, mime.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/media Bild hochladen
multipart/form-data. Nur Rasterformate (jpeg/png/webp/gif), max. 8 MB; SVG ist bewusst ausgeschlossen.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| file | Body | string (binary) | yes | Bilddatei. |
| alt | Body | string | no | Optionaler Alt-Text (BFSG). |
Request · multipart/form-data
{
"file": "<binary>",
"alt": "<string>"
}Responses
201{url, path, bytes, mime, width, height, asset_id}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Ungültige Datei.
PATCH /sites/{site}/media/{asset} Alt-Text/Titel eines Assets setzen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| asset | Path | string | yes | Asset-UUID. |
| alt | Body | string | no | Alt-Text (BFSG). |
| title | Body | string | no | Optionaler Titel. |
Request · application/json
{
"alt": "<string>",
"title": "<string>"
}Responses
200{data:Asset}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
DELETE /sites/{site}/media/{asset} Asset löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| asset | Path | string | yes | Asset-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/media/{asset}/describe Alt-Text/Titel per KI-Vision erzeugen
Claude-Vision analysiert das Bild und schlägt Alt-Text + Titel vor. Rate-Limit 20/min.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| asset | Path | string | yes | Asset-UUID. |
Responses
200{alt, title}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Not found (does not exist, or belongs to another account)429Too many requests (rate limit)
Block-Typen
Globaler Katalog verfügbarer Blocktypen inkl. Schema.
GET /block-types Block-Typ-Katalog
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| all | Query | boolean | no | 1 = auch deaktivierte Typen zeigen. |
Responses
200{data:[BlockType]} mit key, category, schema, default_props, preview_svg.401Not authenticated (missing or invalid bearer token)
CMS
Strukturierte Inhalts-Collections und ihre Einträge je Site.
GET /sites/{site}/collections Collections einer Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:[Collection]} mit name, fields-Schema, Eintragszahl.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/collections Collection anlegen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| name | Body | string | yes | Anzeigename. |
| slug | Body | string | no | URL-Segment (optional; aus name abgeleitet). |
| fields | Body | array | no | Feld-Definitionen [{key,label,type}]. |
Request · application/json
{
"name": "<string>",
"slug": "<string>",
"fields": []
}Responses
201{data:Collection}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
GET /collections/{collection} Collection abrufen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| collection | Path | string | yes | Collection-UUID. |
Responses
200{data:Collection}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
PUT /collections/{collection} Collection ändern
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| collection | Path | string | yes | Collection-UUID. |
| name | Body | string | no | |
| fields | Body | array | no | Feld-Definitionen. |
Request · application/json
{
"name": "<string>",
"fields": []
}Responses
200{data:Collection}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
DELETE /collections/{collection} Collection löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| collection | Path | string | yes | Collection-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
GET /collections/{collection}/entries Einträge einer Collection
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| collection | Path | string | yes | Collection-UUID. |
Responses
200{data:[Entry]} mit Feldwerten.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /collections/{collection}/entries Eintrag anlegen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| collection | Path | string | yes | Collection-UUID. |
| data | Body | object | yes | Feldwerte gemäß Collection-Schema. |
Request · application/json
{
"data": {}
}Responses
201{data:Entry}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
PUT /entries/{entry} Eintrag ändern
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| entry | Path | string | yes | Entry-UUID. |
| data | Body | object | no | Feldwerte. |
Request · application/json
{
"data": {}
}Responses
200{data:Entry}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
DELETE /entries/{entry} Eintrag löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| entry | Path | string | yes | Entry-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Versionen
Snapshots einer Site: sichern und wiederherstellen.
GET /sites/{site}/versions Versionsverlauf einer Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:[SiteVersion]} (neueste zuerst) mit label, created_at, Auslöser.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/versions Version sichern (Snapshot)
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| label | Body | string | no | Optionales Versionslabel. |
Request · application/json
{
"label": "<string>"
}Responses
201{data:SiteVersion}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/versions/{version}/restore Version wiederherstellen
Setzt die Site auf den Snapshot zurück. Der aktuelle Stand wird vorher automatisch als Version gesichert.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| version | Path | string | yes | Versions-UUID. |
Responses
200{ok:true, restored}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Aktivität
Änderungsverlauf einer Site (KI-Läufe, Versionen, Hand-Edits).
GET /sites/{site}/activity Änderungsverlauf einer Site
Zusammengeführter Feed aus KI-Läufen, gesicherten Versionen und Hand-Edits (neueste zuerst).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:[Activity]} mit type, summary, created_at.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
KI-Pipeline
KI-gestütztes Planen, Bauen, Chatten, Text-Überarbeitung, Bild- und Logo-Erzeugung.
POST /ai/plan Blueprint planen (Sitemap + Skeletons)
Zwei-Phasen-Plan (nur lesend, keine Writes). Zusätzlich auf 30/min pro Nutzer gedrosselt.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site_id | Body | string | yes | Site-UUID (36 Zeichen). |
| brief | Body | string | yes | Geschäfts-Briefing (3–8000 Zeichen). |
| lang | Body | string | no | de|en. |
Request · application/json
{
"site_id": "<string>",
"brief": "<string>",
"lang": "<string>"
}Responses
200{generation_id, blueprint:{sitemap,skeletons}, assistant_text, usage}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Site nicht gefunden.422Validation error429Too many requests (rate limit)
POST /ai/build Site bauen (asynchron)
Reiht einen Build-Job ein (Dutzende KI-Calls) und antwortet sofort. brief ODER blueprint nötig.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site_id | Body | string | yes | |
| brief | Body | string | no | Alternativ zu blueprint. |
| blueprint | Body | object | no | Blueprint aus /ai/plan. |
| lang | Body | string | no | de|en. |
Request · application/json
{
"site_id": "<string>",
"brief": "<string>",
"blueprint": {},
"lang": "<string>"
}Responses
202{generation_id, status:"queued"}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Not found (does not exist, or belongs to another account)422Weder brief noch blueprint.429Too many requests (rate limit)
POST /ai/chat Editor-Assistent (SSE-Streaming)
Interaktives Editieren via Tool-Calls. Antwort ist ein text/event-stream; Mutationen werden ggf. als pending_action zur Bestätigung ausgegeben.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site_id | Body | string | yes | |
| message | Body | string | yes | 1–8000 Zeichen. |
| conversation_id | Body | string | no | |
| history | Body | array | no | Bisherige Turns (max. 40). |
| lang | Body | string | no | de|en. |
| page_id | Body | string | no | Aktuell geöffnete Seite (Kontext). |
| block_id | Body | string | no | Aktuell markierter Block (Kontext). |
Request · application/json
{
"site_id": "<string>",
"message": "<string>",
"conversation_id": "<string>",
"history": [],
"lang": "<string>",
"page_id": "<string>",
"block_id": "<string>"
}Responses
200Server-sent events stream (text/event-stream): events start, delta, tool_call_*, pending_action, done, error401Not authenticated (missing or invalid bearer token)402Chat-Token-Kontingent erschöpft.404Not found (does not exist, or belongs to another account)422Validation error429Too many requests (rate limit)
POST /ai/chat/{pendingAction}/apply Vorgeschlagene Mutation bestätigen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| pendingAction | Path | string | yes | ID der Pending-Mutation aus dem Chat-Stream. |
Responses
200Ergebnis der ausgeführten Aktion.401Not authenticated (missing or invalid bearer token)404Nicht gefunden / abgelaufen / kein Zugriff.422Validation error
POST /ai/image Bild generieren (Gemini)
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site_id | Body | string | yes | |
| prompt | Body | string | yes | Bildbeschreibung (3–2000 Zeichen). |
| alt | Body | string | yes | Alt-Text (Pflicht, BFSG). |
| aspect | Body | string | no | 1:1|4:3|16:9|3:4|9:16 (Default 16:9). |
| premium | Body | boolean | no | Höhere Qualität (teurer). |
Request · application/json
{
"site_id": "<string>",
"prompt": "<string>",
"alt": "<string>",
"aspect": "<string>",
"premium": false
}Responses
200Generiertes Asset (url/alt/…).401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Not found (does not exist, or belongs to another account)422Validation error429Too many requests (rate limit)
POST /ai/rewrite Textbaustein überarbeiten
Überarbeitet einen markierten Text nach festem Kommando. Vorhandene ==Hervorhebungs==-Marker und Markdown bleiben erhalten; die Sprache des Textes wird beibehalten. Verbraucht eine ai_text-Einheit.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site_id | Body | string | yes | Site-UUID (36 Zeichen). |
| text | Body | string | yes | Ausgangstext (1–6000 Zeichen). |
| command | Body | string | yes | shorten|lengthen|bullets|professional|simplify|rephrase|fix. |
| lang | Body | string | no | Zielsprache; ohne Angabe die Standardsprache der Site. · max. 5 Zeichen |
Request · application/json
{
"site_id": "<string>",
"text": "<string>",
"command": "shorten",
"lang": "<string>"
}Responses
200{text, command, generation_id, usage}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Site nicht gefunden.422Ungültige Eingabe oder kein verwertbares Ergebnis.429Rate-Limit oder Kontingentsperre belegt (busy).503Textveredelung derzeit nicht verfügbar.
POST /ai/logo Logo als SVG erzeugen
Erzeugt eine Wortmarke, ein Lockup oder ein Monogramm. Das Ergebnis wird serverseitig bereinigt (nur Vektor-Elemente, keine Skripte, keine externen Referenzen). Verbraucht eine ai_text-Einheit.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site_id | Body | string | yes | Site-UUID (36 Zeichen). |
| text | Body | string | no | Text der Marke; ohne Angabe der Site-Name. · max. 60 Zeichen |
| variant | Body | string | no | wordmark (Vorgabe) | lockup | monogram. |
| primary | Body | string | no | Primärfarbe als #rrggbb. · Muster ^#[0-9a-fA-F]{6}$ |
| accent | Body | string | no | Akzentfarbe als #rrggbb. · Muster ^#[0-9a-fA-F]{6}$ |
| hint | Body | string | no | Zusätzlicher Gestaltungshinweis. · max. 300 Zeichen |
Request · application/json
{
"site_id": "<string>",
"text": "<string>",
"variant": "wordmark",
"primary": "<string>",
"accent": "<string>",
"hint": "<string>"
}Responses
200{svg, variant, generation_id, usage}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Site nicht gefunden.422Ungültige Eingabe oder kein verwertbares SVG erhalten.429Rate-Limit oder Kontingentsperre belegt (busy).503Logo-Erzeugung derzeit nicht verfügbar.
POST /ai/brand-extract Markenvorschlag aus bestehender Website
Ruft eine öffentliche http(s)-Adresse serverseitig ab, wertet Titel, Meta-Beschreibung, og:site_name, theme-color und einen Textauszug aus und leitet daraus einen Markenvorschlag ab. Nicht öffentlich erreichbare Ziele werden abgelehnt. Verbraucht eine ai_text-Einheit.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| url | Body | string | yes | Öffentliche http(s)-Adresse. · max. 300 Zeichen |
| site_id | Body | string | no | Optional — ordnet den Lauf einer Site zu (36 Zeichen). |
Request · application/json
{
"url": "<string>",
"site_id": "<string>"
}Responses
200{brand:{name,tagline,industry,tone,brand_voice,colors:{primary,accent},summary}, source_url, usage}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Site nicht gefunden (nur bei gesetzter site_id).422Adresse ungültig/nicht erreichbar oder kein verwertbares Ergebnis.429Rate-Limit oder Kontingentsperre belegt (busy).503Marken-Analyse derzeit nicht verfügbar.
GET /ai/generations/{generation} Status einer Generierung (Polling)
Fortschritt von Build/Publish. Großzügiger Limiter (120/min) für sekündliches Polling.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| generation | Path | string | yes | Generierungs-UUID. |
Responses
200{id, kind, status, output, error}; status queued|running|succeeded|failed.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Blog
KI-Blog je Site: Themenvorschläge und Beitrags-Erzeugung.
POST /sites/{site}/blog/topics Themenvorschläge für den Blog
Synchron. Verbraucht eine ai_text-Einheit.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| count | Body | integer | no | Anzahl Vorschläge, 1–8 (Vorgabe 5). |
Request · application/json
{
"count": 1
}Responses
200{topics:[{title, angle, category, tags}]}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Not found (does not exist, or belongs to another account)422Validation error429Rate-Limit oder Kontingentsperre belegt (busy).503Themenvorschläge derzeit nicht verfügbar.
POST /sites/{site}/blog/generate Blogbeitrag erzeugen (asynchron)
Reiht die Erzeugung ein und antwortet sofort. Fortschritt über GET /ai/generations/{generation}; bei status=succeeded trägt output {page_id, slug, title}. Der Beitrag entsteht als Seite mit type=post — Auflisten, Ändern und Löschen laufen über die Seiten-Endpunkte. Verbraucht eine ai_text-Einheit; schlägt der Lauf fehl, wird sie erstattet.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| topic | Body | string | yes | Thema (3–200 Zeichen). |
| category | Body | string | no | Optionale Kategorie. · max. 60 Zeichen |
| tags | Body | array | no | Bis zu 6 Schlagwörter à 40 Zeichen. |
| status | Body | string | no | draft (Vorgabe) | scheduled | published. |
| publish_at | Body | string (date) | no | Pflicht bei status=scheduled; frühestens heute. |
Request · application/json
{
"topic": "<string>",
"category": "<string>",
"tags": [],
"status": "draft",
"publish_at": "<date>"
}Responses
202{generation_id, status:"queued"}.401Not authenticated (missing or invalid bearer token)402Quota used up — upgrade or top up required404Not found (does not exist, or belongs to another account)422Validation error429Rate-Limit oder Kontingentsperre belegt (busy).500Start fehlgeschlagen (dispatch_failed) — verbrauchte Credits werden erstattet.
Assistent
Verwaltung des eingebetteten Site-Assistenten (RAG).
GET /sites/{site}/assistant Assistent-Status & Einstellungen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{enabled, settings, knowledge, chat_tokens, embed}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
PUT /sites/{site}/assistant Assistent an/aus + Einstellungen
Aktivieren erfordert mindestens den Starter-Plan (Feature ai_chat).
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| enabled | Body | boolean | no | |
| settings | Body | object | no | name, greeting, placeholder, persona, primary, accent, show_branding. |
Request · application/json
{
"enabled": false,
"settings": {}
}Responses
200{enabled, settings, knowledge, embed}.401Not authenticated (missing or invalid bearer token)403Plan reicht nicht (ai_chat).404Not found (does not exist, or belongs to another account)
POST /sites/{site}/assistant/reindex RAG-Wissensbasis neu aufbauen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{ok:true, knowledge}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)500Neuaufbau fehlgeschlagen.
Assistent (öffentlich)
Widget-Endpunkte für veröffentlichte Sites (ohne Token).
GET /public/assistant Erreichbarkeits-Probe des Widget-Gates no token
Responses
200{ok:true, service:"xicflow-assistant"}.
GET /public/assistant/widget.js Eingebettetes Widget-Skript no token
Responses
200JavaScript (application/javascript).404Skript nicht vorhanden.
GET /public/assistant/{siteSlug}/config Widget-Konfiguration (Branding) no token
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| siteSlug | Path | string | yes | Slug der veröffentlichten Site. |
Responses
200{enabled, name, greeting, placeholder, locale, colors, branding} (enabled:false wenn nicht freigeschaltet).
POST /public/assistant/{siteSlug}/chat Geerdete Q&A des Site-Assistenten (SSE) no token
Read-only, an der veröffentlichten Wissensbasis geerdet. Body als text/plain-JSON (CORS-simpel): {message, history?, conversation_id?, lang?}. Abrechnung gegen den Chat-Token-Pool des Site-Tenants.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| message | Body | string | yes | Besucherfrage (max. 4000 Zeichen). |
| history | Body | array | no | |
| conversation_id | Body | string | no | |
| lang | Body | string | no | de|en. |
Request · text/plain
{
"message": "<string>",
"history": [],
"conversation_id": "<string>",
"lang": "<string>"
}Responses
200Server-sent events stream (text/event-stream): events start, delta, tool_call_*, pending_action, done, error404Assistent nicht verfügbar.422Leere/zu lange Nachricht.
Kontaktanfragen
Formular-Eingänge veröffentlichter Sites.
POST /public/contact/{siteSlug} Formular-Eingang (öffentlich) no token
Cross-Origin von veröffentlichten Sites (kein Token). Honeypot-Feld "website" muss leer bleiben; still-quittierend bei Spam/ungültig (kein Enumerationssignal). 10/min pro IP.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| siteSlug | Path | string | yes | Slug der veröffentlichten Site. |
| name | Body | string | yes | |
| Body | string (email) | yes | ||
| phone | Body | string | no | |
| message | Body | string | no | Nachricht ODER url erforderlich. |
| url | Body | string | no | |
| website | Body | string | no | Honeypot — leer lassen. |
Request · text/plain
{
"name": "<string>",
"email": "<email>",
"phone": "<string>",
"message": "<string>",
"url": "<string>",
"website": "<string>"
}Responses
200{ok:true} (immer, auch bei stillem Reject).429Too many requests (rate limit)
GET /sites/{site}/submissions Eingänge einer Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:[ContactSubmission]} (neueste zuerst, max. 500).401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
PATCH /submissions/{submission} Status setzen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| submission | Path | string | yes | Submission-UUID. |
| status | Body | string | yes | new|read|archived. |
Request · application/json
{
"status": "<string>"
}Responses
200{data:ContactSubmission}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Validation error
DELETE /submissions/{submission} Eingang löschen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| submission | Path | string | yes | Submission-UUID. |
Responses
200{deleted:true}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
GET /sites/{site}/submissions/export Eingänge als CSV exportieren
text/csv (Semikolon-Delimiter + UTF-8-BOM, DE-Excel-tauglich). Custom-Formularfelder werden als zusätzliche Spalten ergänzt.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200CSV-Datei (Content-Disposition: attachment).401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Domains
Custom-Domains: anmelden, verifizieren, binden.
GET /sites/{site}/domains Domains der Site
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
Responses
200{data:[Domain]} inkl. dns_instructions.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
POST /sites/{site}/domains Domain anmelden
Preview-Subdomains (*.xicflow.com) werden sofort verifiziert und gebunden.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| site | Path | string | yes | Site-UUID. |
| host | Body | string | yes | Reiner Hostname (kein Schema/Pfad). |
| is_primary | Body | boolean | no | |
| redirect_to | Body | string | no | Optionales Redirect-Ziel. |
Request · application/json
{
"host": "<string>",
"is_primary": false,
"redirect_to": "<string>"
}Responses
201{data:Domain, bind}.401Not authenticated (missing or invalid bearer token)403Custom-Domain-Limit erreicht.404Not found (does not exist, or belongs to another account)422Host bereits registriert / ungültig.
POST /domains/{domain}/verify Domain-Besitz verifizieren (TXT)
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| domain | Path | string | yes | Domain-UUID. |
Responses
200{verified:true, data:Domain}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)422Noch nicht verifiziert (reason/checked).
POST /domains/{domain}/bind Domain binden (bis live)
Fährt die State-Machine (Plesk-Alias + LE + nginx-Map). Vorbedingung: verifiziert.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| domain | Path | string | yes | Domain-UUID. |
Responses
200{ok:true, state:"live", steps}.202Angenommen — läuft weiter (DNS noch nicht aufgelöst / manueller Schritt).401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)409Noch nicht verifiziert.422Fehlerzustand.
DELETE /domains/{domain} Domain entfernen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| domain | Path | string | yes | Domain-UUID. |
Responses
200{ok:true, detach}.401Not authenticated (missing or invalid bearer token)404Not found (does not exist, or belongs to another account)
Nutzung
KI-Kontingente und Verbrauch des Kontos.
GET /usage KI-Kontingente & Verbrauch
Responses
200{plan, quotas, used, remaining, ai_credits, chat_tokens} (-1 = unbegrenzt).401Not authenticated (missing or invalid bearer token)
Billing
Stripe-Abos, Credit-/Token-Pakete, Add-ons, Rechnungen, Webhook.
GET /billing/config Stripe-Public-Key & aktueller Plan
Responses
200{publicKey, mode, currentPlan, hasSubscription, prices}.401Not authenticated (missing or invalid bearer token)
GET /billing/plans Kaufbarer Katalog
Abo-Tarife (Starter/Pro/Premium/Agentur), Credit- und Chat-Token-Pakete. Preise NETTO in Cent + EUR.
Responses
200{currentPlan, currency, plans, creditPacks, creditBalance, chatTokenPacks, chatTokens}.401Not authenticated (missing or invalid bearer token)
POST /billing/checkout Checkout starten (Abo / Credits / Chat-Tokens)
target steuert die Variante: plan (Default, mode=subscription) | credits | chat_tokens (mode=payment). Aktives Abo -> sofortiges In-Place-Upgrade mit Proration.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| target | Body | string | no | plan|credits|chat_tokens. |
| plan | Body | string | no | starter|pro|premium|agency (bei target=plan). |
| interval | Body | string | no | month|year (bei target=plan). |
| pack | Body | string | no | Pack-Key (bei credits/chat_tokens). |
Request · application/json
{
"target": "<string>",
"plan": "<string>",
"interval": "<string>",
"pack": "<string>"
}Responses
200{sessionId, url} oder {switched:true, plan} bei In-Place-Upgrade.401Not authenticated (missing or invalid bearer token)422Kein Tenant / unbekanntes Paket / ungültige Eingabe.
POST /billing/portal Stripe-Kundenportal
Responses
200{url}.400Kein Stripe-Konto.401Not authenticated (missing or invalid bearer token)
GET /billing/subscription Aktuelle Abo-Details
Responses
200{active, status, plan, current_period_end, cancel_at_period_end, next_charge_amount, interval …}.401Not authenticated (missing or invalid bearer token)
GET /billing/invoices Lokale GoBD-Rechnungen
Responses
200{invoices:[…]}.401Not authenticated (missing or invalid bearer token)
GET /billing/invoices/{invoiceId}/pdf Rechnung als PDF herunterladen
Nur Rechnungen des eigenen Kontos. Der Dateiname ist die Rechnungsnummer.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| invoiceId | Path | string | yes | Rechnungs-UUID aus GET /billing/invoices. |
Responses
200PDF-Datei (application/pdf, Content-Disposition: attachment).401Not authenticated (missing or invalid bearer token)404Rechnung nicht gefunden, ohne PDF oder Datei nicht vorhanden (file_missing).
POST /billing/cancel Abo kündigen
Standard zum Periodenende; ?immediate=true kündigt sofort.
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| immediate | Query | boolean | no | true = sofort statt zum Periodenende. |
Responses
200{ok:true, status, cancel_at_period_end, current_period_end}.400Kein Tenant / keine Subscription.401Not authenticated (missing or invalid bearer token)500Server error
POST /billing/resume Geplante Kündigung zurücknehmen
Responses
200{ok:true, cancel_at_period_end:false}.400Keine Subscription.401Not authenticated (missing or invalid bearer token)500Server error
GET /billing/addons Add-on-Katalog
Responses
200{addons}.401Not authenticated (missing or invalid bearer token)
POST /billing/addons/checkout Add-ons buchen
Parameters
| Name | In | Type | Required | Note |
|---|---|---|---|---|
| addons | Body | array | yes | Liste {key, quantity?} (extra_site, extra_seat, custom_domain, ki_generations, image_credit_pack). |
Request · application/json
{
"addons": []
}Responses
200{sessionId, url}.401Not authenticated (missing or invalid bearer token)422Kein Tenant / unbekanntes Add-on.
POST /webhooks/stripe Stripe-Webhook (öffentlich, signaturverifiziert) no token
Kein Bearer-Token. Signatur über Stripe-Signature-Header; Idempotenz via stripe_events. Roh-Body erforderlich.
Responses
200{received:true}.400Signatur fehlt/ungültig.500Kein Webhook-Secret konfiguriert.
Webhook for new requests
XICflow calls an address of your choice by itself as soon as a form has been submitted on a published website.
You enter the target address per website in the “Automation webhook (URL)” field; only https addresses are accepted. The call happens in parallel with a five-second time limit — if the receiver does not answer, the request is still not lost, it is in the requests inbox in any case. There is no automatic retry.
If a signing secret is stored, every call carries the headers “X-XICflow-Timestamp” and “X-XICflow-Signature”. The receiver computes HMAC-SHA256 over the timestamp, a dot and the unchanged body, and compares the result with the value behind “sha256=”. The timestamp protects against a recorded call being replayed later.
Shape of the call
POST
· Event form.submission
| Field | Type | Note |
|---|---|---|
| event | string | Werte: form.submission |
| site | string | Slug der Site. |
| submitted_at | string (date-time) | |
| submission | object | |
| submission.id | string (uuid) | |
| submission.name | string | |
| submission.email | string | |
| submission.phone | string | |
| submission.message | string | |
| submission.url | string | |
| submission.fields | object | Zusaetzliche Form-Builder-Felder. |
| submission.form | string | Kennung des ausgeloesten Formulars (aus dem Formular-Meta). |
{
"event": "form.submission",
"site": "<string>",
"submitted_at": "<date-time>",
"submission": {
"id": "<uuid>",
"name": "<string>",
"email": "<string>",
"phone": "<string>",
"message": "<string>",
"url": "<string>",
"fields": {},
"form": "<string>"
}
}Machine-readable version
The same description as OpenAPI 3.1 — for tools that generate calls, types or a reference of their own from it.
The specification is freely available and needs no token. The version served by the interface itself is always authoritative; the copy next to this documentation is produced from the same source when the page is built.
https://api.xicflow.com/api/openapi.json- Specification from the interface — always the current state.
- Copy next to this documentation — the state of the last build of this page.
- The interface reference as a web page — generated from the same specification.
You can load the file into a tool of your choice to try out calls or generate matching data types for your programming language. Signing in stays the same: a bearer token from /auth/login.