Auf dieser Seite
API
Alles, was Sie im Editor tun, läuft über eine offene Schnittstelle — dieselbe, die Sie selbst ansprechen können. Dieses Kapitel erklärt Basis-Adresse, Anmeldung und Fehlerformat und führt danach jeden Endpunkt auf, der Ihrem Konto offensteht.
Wofür die API da ist
Die Schnittstelle ist die Steuerebene von XICflow: Websites, Seiten, Bausteine, Medien, KI-Läufe, Domains, Anfragen und Abrechnung.
Der Editor arbeitet ausschließlich über diese Schnittstelle. Was Sie dort per Maus erledigen, können Sie deshalb auch aus einem eigenen Programm heraus tun — etwa Inhalte aus einem anderen System übernehmen, Anfragen abholen oder eine Veröffentlichung anstoßen.
| Basis-Adresse | https://api.xicflow.com/api |
|---|---|
| Format | JSON in UTF-8, auch im Fehlerfall. Ausnahmen sind der Bild-Upload (multipart/form-data), der CSV-Export, der Rechnungs-Download als PDF und die beiden Chat-Endpunkte, die einen Ereignisstrom (text/event-stream) liefern. |
| Version | Die Spezifikation trägt die Version 1.0.0. Die Adresse selbst enthält keine Versionsnummer; maßgeblich ist immer die maschinenlesbare Fassung. |
| Umfang | 115 Operationen auf 92 Pfaden, gegliedert in 19 Kategorien. |
| Trennung der Konten | Jede Ressource gehört zu genau einem Konto. Eine Kennung aus einem fremden Konto beantwortet die API mit 404. |
| Rollen | Lesen steht allen Mitgliedern offen. Inhaltliche Änderungen verlangen mindestens „Bearbeiter“, das Anlegen und Löschen von Websites und Domains mindestens „Administrator“, Abrechnungsvorgänge die Rolle „Eigentümer“. |
Aufrufe aus dem Browser sind auf zwei Herkünfte begrenzt
Für Anfragen aus einer Webseite heraus lässt die Schnittstelle nur xicflow.com und www.xicflow.com zu. Die öffentlichen Endpunkte für Kontaktformular und Site-Assistent sind davon ausgenommen, weil veröffentlichte Websites unter eigener Domain laufen. Ein Programm auf Ihrem Server ist von dieser Begrenzung nicht betroffen.
Anmelden und Token mitschicken
Ein Token entsteht bei der Anmeldung und wird danach bei jeder Anfrage im Kopfzeilenfeld „Authorization“ mitgeschickt.
-
Rufen Sie POST /auth/login mit E-Mail und Passwort auf.
Ein neues Konto legen Sie stattdessen über POST /auth/register an; auch dieser Aufruf liefert bereits ein Token.
-
Nehmen Sie das Feld „token“ aus der Antwort.
Das Feld „access_token“ enthält denselben Wert und existiert nur der Kompatibilität halber.
-
Schicken Sie bei jeder weiteren Anfrage die Kopfzeile „Authorization: Bearer <token>“ mit.
-
Beenden Sie die Sitzung über POST /auth/logout, wenn Sie das Token nicht mehr brauchen.
Ein Token bleibt gültig, bis es widerrufen wird — einzeln über /auth/logout oder DELETE /auth/sessions/{id}, alle übrigen auf einmal über 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>"
}
}
}Mit dem Token sind alle übrigen Endpunkte erreichbar:
curl https://api.xicflow.com/api/sites \
-H "Authorization: Bearer <token>"Ist für das Konto die Zwei-Faktor-Anmeldung aktiv und fehlt der Code, antwortet /auth/login nicht mit einem Token, sondern mit „totp_required“ und einem kurzlebigen „challenge_token“ (zehn Minuten gültig). Sie lösen es entweder über POST /auth/totp/challenge ein oder wiederholen den Login mit dem Feld „totp_code“. Ein Backup-Code wird an derselben Stelle angenommen.
Einige Endpunkte kommen ohne Token aus — Anmeldung und Registrierung, der Rückweg der Google-Anmeldung, die Passkey-Anmeldung, die Einlösung der Zwei-Faktor-Abfrage, das Kontaktformular veröffentlichter Websites, der eingebettete Site-Assistent, die Vorschau und Annahme einer Team-Einladung sowie der Stripe-Rückruf. In der Referenz weiter unten tragen sie die Kennzeichnung „ohne Token“.
Konventionen und Fehlerformat
Fehler kommen als JSON zurück. Je nach Ursache tragen sie ein Feld „error“ mit einem festen Schlüssel oder eine Feldliste unter „errors“.
Ungültige Eingaben (422)
Verletzt eine Anfrage die Feldregeln, nennt die Antwort jedes betroffene Feld einzeln.
{
"message": "<Meldung>",
"errors": {
"<Feldname>": ["<Meldung zum Feld>"]
}
}Rolle reicht nicht (403)
„required“ nennt die nötige, „role“ die tatsächliche Rolle.
{
"error": "forbidden",
"message": "Ihre Rolle (…) erlaubt diese Aktion nicht.",
"required": "editor",
"role": "viewer"
}Kontingent erschöpft (402)
Das Feld „resource“ nennt das betroffene Kontingent. Beim Editor-Chat lautet der Schlüssel stattdessen „chat_tokens_exhausted“.
{
"error": "quota_exceeded",
"resource": "ai_image",
"plan": "<Tarif>",
"remaining": 0,
"upgrade": true,
"message": "Dein monatliches Kontingent für KI-Bilder ist erschöpft. …"
}Konto gesperrt (403)
Ist das Konto wegen offener Rechnungen gesperrt, antwortet jeder Endpunkt mit Token so — unabhängig davon, was angefragt wurde.
{
"error": "account_suspended",
"message": "Dein Konto ist wegen offener Rechnungen gesperrt. …"
}Verwendete Statuscodes
| Code | Bedeutung |
|---|---|
| 200 | Erfolg. |
| 201 | Angelegt. |
| 202 | Angenommen, wird im Hintergrund verarbeitet — der Fortschritt läuft über GET /ai/generations/{generation}. |
| 302 | Weiterleitung; kommt nur bei den beiden Endpunkten der Google-Anmeldung vor, die eine Browser-Navigation sind. |
| 400 | Ungültige Anfrage. |
| 401 | Kein oder ungültiges Token, falsche Zugangsdaten oder falscher Zwei-Faktor-Code. |
| 402 | Kontingent erschöpft — Tarif erweitern oder aufladen. |
| 403 | Kein Zugriff: Rolle reicht nicht oder Konto gesperrt. |
| 404 | Nicht vorhanden — oder Ressource eines fremden Kontos. |
| 409 | Konflikt: eine Vorbedingung ist nicht erfüllt. |
| 410 | Nicht mehr gültig, etwa eine bereits eingelöste oder abgelaufene Einladung. |
| 422 | Feldregeln verletzt. |
| 429 | Zu viele Anfragen in kurzer Zeit. |
| 500 | Serverfehler. |
| 503 | Vorübergehend nicht verfügbar. |
Grenzen je Minute
Empfindliche und teure Endpunkte sind gedrosselt. Ist die Grenze erreicht, antwortet die API mit 429.
Gezählt wird je angefangener Minute. Bei Endpunkten ohne Token zählt die Absender-Adresse, bei allen übrigen das angemeldete Konto. Ein allgemeines Limit über die gesamte Schnittstelle gibt es nicht — nur die hier genannten Endpunkte sind begrenzt.
| Endpunkte | Je Minute |
|---|---|
| Passwort ändern, Zwei-Faktor aktivieren oder abschalten, Backup-Codes neu erzeugen, Datenexport | 6 |
| Kontaktformular veröffentlichter Websites, Team-Einladung verschicken | 10 |
| Anmeldung, Registrierung, Zwei-Faktor-Abfrage, Passkey-Anmeldung, Rückweg der Google-Anmeldung, Anzeigename ändern, alle anderen Sitzungen abmelden, Einladung ansehen und annehmen | 12 |
| Einzelne Sitzung abmelden, Alt-Text per KI erzeugen, Chat des öffentlichen Site-Assistenten | 20 |
| Alle KI-Endpunkte, Blog-Erzeugung, Veröffentlichen, Bild-Upload, Domain prüfen und binden, Wissensbasis neu aufbauen, Oberflächensprache setzen, alle kaufmännischen Vorgänge | 30 |
| Konfiguration des öffentlichen Site-Assistenten | 60 |
| Status einer Generierung abfragen | 120 |
Lange Läufe und Wiederholungen
Bauen und Veröffentlichen dauern zu lange für eine einzelne Antwort. Diese Endpunkte quittieren sofort und arbeiten im Hintergrund weiter.
-
Der Aufruf antwortet mit 202 und einer „generation_id“.
So arbeiten POST /ai/build, POST /sites/{site}/publish und POST /sites/{site}/blog/generate.
-
Fragen Sie GET /ai/generations/{generation} ab, bis „status“ nicht mehr queued oder running lautet.
Dieser Endpunkt ist mit 120 Abfragen je Minute großzügig bemessen, sekündliches Nachfragen ist also vorgesehen.
-
Bei „succeeded“ steht das Ergebnis im Feld „output“, bei „failed“ die Ursache im Feld „error“.
Ein fehlgeschlagener Lauf wird nicht von selbst wiederholt
Bau und Veröffentlichung laufen bewusst genau einmal, weil ein zweiter Anlauf nicht dasselbe Ergebnis liefern würde. Steht „failed“, stoßen Sie den Vorgang selbst erneut an. Auch sonst kennt die Schnittstelle keine Wiederholungsschlüssel: wird dieselbe erzeugende Anfrage zweimal geschickt, entsteht ein zweiter Datensatz.
Zwei Endpunkte antworten nicht mit einem fertigen Dokument, sondern mit einem laufenden Ereignisstrom: der Editor-Assistent unter POST /ai/chat und der Assistent veröffentlichter Websites unter POST /public/assistant/{siteSlug}/chat. Beide senden Ereignisse als text/event-stream, beginnend mit „start“ und endend mit „done“ oder „error“.
Referenz aller Endpunkte
Jeder Eintrag lässt sich aufklappen und zeigt dann Zweck, Parameter, Aufbau der Anfrage und mögliche Antworten.
Diese Liste ist erzeugt, nicht abgeschrieben
Sie entsteht beim Bau der Seite aus derselben Spezifikation, die auch die Schnittstelle selbst ausliefert. Kommt ein Endpunkt hinzu, steht er nach dem nächsten Bau hier — dafür bleiben Kurztexte, Feldhinweise und die besonderen Antwortbeschreibungen auch in der englischen Fassung deutsch; übersetzt sind lediglich die wiederkehrenden Standardantworten. Endpunkte des Betreibers sind nicht enthalten.
Auth
Registrierung, Login, Sitzungen, 2FA/TOTP, Passkeys, OAuth.
POST /auth/register Konto registrieren ohne Token
Legt Nutzer + eigenen Tenant (Owner) an und liefert ein Bearer-Token. 14-Tage-Trial.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| Rumpf | string (email) | ja | E-Mail (wird normalisiert, +alias entfernt). | |
| password | Rumpf | string | ja | Mindestens 10 Zeichen. |
| name | Rumpf | string | ja | Anzeigename. |
Anfrage · application/json
{
"email": "<email>",
"password": "<string>",
"name": "<string>"
}Antworten
200Token + Nutzerprofil ({token, access_token, token_type, user}).422E-Mail bereits vergeben oder ungültige Eingabe.
POST /auth/login Login (Passwort, optional 2FA) ohne Token
Liefert {token, user}. Bei aktivem TOTP ohne totp_code stattdessen {totp_required:true, challenge_token, expires_in}.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| Rumpf | string (email) | ja | ||
| password | Rumpf | string | ja | |
| totp_code | Rumpf | string | nein | Optionaler 6-stelliger TOTP- oder Backup-Code. |
Anfrage · application/json
{
"email": "<email>",
"password": "<string>",
"totp_code": "<string>"
}Antworten
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 ohne Token
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| challenge_token | Rumpf | string | ja | 48-stelliges Token aus der Login-Antwort. |
| code | Rumpf | string | ja | TOTP- oder Backup-Code. |
Anfrage · application/json
{
"challenge_token": "<string>",
"code": "<string>"
}Antworten
200{token,user}.401Ungültig/abgelaufen.429Zu viele Versuche.
POST /auth/passkey/login/options Passkey-Login: Optionen anfordern ohne Token
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| Rumpf | string (email) | nein | Optional — schränkt allowCredentials ein. |
Anfrage · application/json
{
"email": "<email>"
}Antworten
200WebAuthn-publicKey-Request-Optionen (flach).
POST /auth/passkey/login/verify Passkey-Login: Antwort verifizieren ohne Token
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| id | Rumpf | string | ja | Credential-ID (base64url). |
| response | Rumpf | object | ja | WebAuthn-Assertion: clientDataJSON, authenticatorData, signature. |
Anfrage · application/json
{
"id": "<string>",
"response": {}
}Antworten
200{token,user}.401Challenge abgelaufen / Passkey unbekannt.403Konto gesperrt.
GET /auth/oauth/{provider}/redirect Google-SSO starten (302) ohne Token
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| provider | Pfad | string | ja | OAuth-Provider, aktuell nur "google". |
| return | Abfrage | string | nein | Rückkehr-URL nach erfolgreichem Login. |
Antworten
302Weiterleitung (Browser-Navigation)404Unbekannter Provider.
GET /auth/oauth/{provider}/callback Google-SSO-Callback (302 mit Token-Fragment) ohne 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).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| provider | Pfad | string | ja | OAuth-Provider, aktuell nur "google". |
| state | Abfrage | string | nein | CSRF-State. |
| code | Abfrage | string | nein | Authorization-Code. |
Antworten
302Redirect ins Frontend mit #token=…&provider=…(&new=1) im Fragment.
POST /auth/oauth/{provider}/callback SSO-Callback als form_post (302, identisch zu GET) ohne Token
Alias derselben Verarbeitung für Provider mit response_mode=form_post. State und Code werden weiterhin aus der Query gelesen.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| provider | Pfad | string | ja | OAuth-Provider, aktuell nur "google". |
| state | Abfrage | string | nein | CSRF-State. |
| code | Abfrage | string | nein | Authorization-Code. |
Antworten
302Redirect ins Frontend mit #token=… im Fragment.
GET /me Aktuelles Nutzerprofil
Antworten
200Nutzerprofil inkl. tenant (plan/status/trial_ends_at).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
POST /auth/logout Aktuelles Token widerrufen
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
POST /auth/change-password Passwort ändern
Widerruft alle anderen Sessions. new_password braucht Bestätigung (new_password_confirmation).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| current_password | Rumpf | string | ja | |
| new_password | Rumpf | string | ja | Mindestens 10 Zeichen, confirmed. |
| new_password_confirmation | Rumpf | string | nein |
Anfrage · application/json
{
"current_password": "<string>",
"new_password": "<string>",
"new_password_confirmation": "<string>"
}Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges 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.
Antworten
200{sessions:[{id, device, last_used_at, created_at, current}]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
DELETE /auth/sessions Alle anderen Sitzungen abmelden
Widerruft jedes Token außer dem aktuell verwendeten.
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)429Zu viele Anfragen (Rate-Limit)
DELETE /auth/sessions/{id} Einzelne Sitzung abmelden
Nur eigene Sitzungen; eine fremde ID trifft nichts und wird trotzdem mit 200 quittiert.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| id | Pfad | string | ja | Sitzungs-ID aus GET /auth/sessions. |
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)429Zu viele Anfragen (Rate-Limit)
GET /auth/totp/status 2FA-Status
Antworten
200{enabled, enabled_at, has_pending_secret, backup_codes_left}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
POST /auth/totp/setup 2FA einrichten (Pending-Secret)
Antworten
200{secret, provisioning_uri} (otpauth://).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Bereits aktiviert.
POST /auth/totp/enable 2FA aktivieren
Verifiziert den ersten Code und liefert die Backup-Codes EINMALIG im Klartext.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| code | Rumpf | string | ja | Aktueller TOTP-Code. |
Anfrage · application/json
{
"code": "<string>"
}Antworten
200{enabled:true, backup_codes[]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Ungültiger Code / kein Pending-Secret.
POST /auth/totp/disable 2FA deaktivieren
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| password | Rumpf | string | ja | |
| code | Rumpf | string | ja | TOTP- oder Backup-Code. |
Anfrage · application/json
{
"password": "<string>",
"code": "<string>"
}Antworten
200{enabled:false}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Passwort oder Code falsch.
POST /auth/totp/regenerate-codes Backup-Codes neu erzeugen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| password | Rumpf | string | ja | |
| code | Rumpf | string | ja |
Anfrage · application/json
{
"password": "<string>",
"code": "<string>"
}Antworten
200{backup_codes[]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Validierungsfehler
POST /auth/passkey/register/options Passkey registrieren: Optionen
Antworten
200WebAuthn-publicKey-Create-Optionen (flach).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
POST /auth/passkey/register/verify Passkey registrieren: verifizieren
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| id | Rumpf | string | ja | Credential-ID (base64url). |
| response | Rumpf | object | ja | clientDataJSON + attestationObject. |
| name | Rumpf | string | nein | Optionaler Anzeigename. |
Anfrage · application/json
{
"id": "<string>",
"response": {},
"name": "<string>"
}Antworten
200{ok:true, id, credential_name}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Challenge/Attestation ungültig.
GET /auth/passkeys Passkeys auflisten
Antworten
200{passkeys:[{id, credential_name, last_used_at, created_at}]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
PUT /auth/passkeys/{id} Passkey umbenennen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| id | Pfad | string | ja | Credential-ID. |
| name | Rumpf | string | ja |
Anfrage · application/json
{
"name": "<string>"
}Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
DELETE /auth/passkeys/{id} Passkey löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| id | Pfad | string | ja | Credential-ID. |
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
GET /auth/oauth/identities Verknüpfte OAuth-Identitäten
Antworten
200{providers:[…]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
DELETE /auth/oauth/{provider} OAuth-Identität entkoppeln
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| provider | Pfad | string | ja | OAuth-Provider, aktuell nur "google". |
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Letzter Zugang ohne gesetztes Passwort.
Konto
Eigenes Profil, Oberflächensprache und Datenexport (DSGVO Art. 20).
POST /account/profile Eigenen Anzeigenamen ändern
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| name | Rumpf | string | ja | Anzeigename, max. 190 Zeichen (wird getrimmt). |
Anfrage · application/json
{
"name": "<string>"
}Antworten
200{name}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Validierungsfehler429Zu viele Anfragen (Rate-Limit)
POST /account/locale Oberflächensprache setzen
Am Konto gespeichert und damit gerätübergreifend wirksam.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| language | Rumpf | string | ja | de oder en. |
Anfrage · application/json
{
"language": "de"
}Antworten
200{language}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Validierungsfehler429Zu viele Anfragen (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.
Antworten
200{meta, account, users, sites, contact_submissions, invoices, assets}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Kein Konto (Tenant) am Nutzer hinterlegt.429Zu viele Anfragen (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.
Antworten
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}.401Nicht authentifiziert (fehlendes oder ungültiges 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| Rumpf | string (email) | ja | Adresse ohne bestehendes XICflow-Konto. · max. 255 Zeichen | |
| role | Rumpf | string | ja | admin|editor|viewer. |
Anfrage · application/json
{
"email": "<email>",
"role": "admin"
}Antworten
201{invitation:{id,email,role,expires_at,expired}, accept_url}.401Nicht authentifiziert (fehlendes oder ungültiges 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.429Zu viele Anfragen (Rate-Limit)
DELETE /team/invitations/{invitation} Offene Einladung widerrufen
Erfordert mindestens die Rolle admin.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| invitation | Pfad | string | ja | Einladungs-UUID aus GET /team. |
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)403Rolle reicht nicht.404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| member | Pfad | string | ja | Nutzer-UUID aus GET /team. |
| role | Rumpf | string | ja | admin|editor|viewer. |
Anfrage · application/json
{
"role": "admin"
}Antworten
200{ok:true, role}.401Nicht authentifiziert (fehlendes oder ungültiges 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| member | Pfad | string | ja | Nutzer-UUID aus GET /team. |
Antworten
200{ok:true}.401Nicht authentifiziert (fehlendes oder ungültiges 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) ohne Token
Löst den Einmal-Token auf, damit die Annahme-Seite Team und Rolle anzeigen kann. Rate-Limit 12/min.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| token | Pfad | string | ja | Einmal-Token aus dem Einladungslink. |
Antworten
200{email, role, team, expires_at}.404Token unbekannt (invalid).410Bereits angenommen (already_accepted) oder abgelaufen (expired).429Zu viele Anfragen (Rate-Limit)
POST /public/team/accept Einladung annehmen (ohne Anmeldung) ohne 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| token | Rumpf | string | ja | Einmal-Token aus dem Einladungslink. |
| name | Rumpf | string | ja | Anzeigename, max. 255 Zeichen. |
| password | Rumpf | string | ja | Mindestens 10 Zeichen. |
Anfrage · application/json
{
"token": "<string>",
"name": "<string>",
"password": "<string>"
}Antworten
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.422Validierungsfehler429Zu viele Anfragen (Rate-Limit)
Sites
CRUD der Websites eines Kontos plus Veröffentlichung.
GET /sites Sites des Kontos auflisten
Antworten
200{data:[Site]} inkl. theme, domains, pages_count.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
POST /sites Site anlegen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| name | Rumpf | string | ja | |
| slug | Rumpf | string | ja | Kleinbuchstaben/Ziffern/Bindestrich, je Konto eindeutig. |
| theme_id | Rumpf | string (uuid) | nein | Optionales Theme (UUID, eigen oder System). |
| default_locale | Rumpf | string | nein | Default "de". |
| locales | Rumpf | array | nein | Aktive Sprachen, z. B. ["de","en"]. |
| company | Rumpf | object | nein | Firmenstammdaten. |
| branding | Rumpf | object | nein | |
| trust_bar | Rumpf | object | nein | |
| xictraq_id | Rumpf | string | nein | XICTRAQ-Property für Analytics/Monitoring. |
| contact_endpoint | Rumpf | string | nein | |
| contact_recipient | Rumpf | string | nein | |
| google_verification | Rumpf | string | nein | |
| settings | Rumpf | object | nein |
Anfrage · 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": {}
}Antworten
201{data:Site}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Slug vergeben / ungültig.
GET /sites/{site} Site abrufen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:Site} inkl. pages, theme, domains.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
PUT /sites/{site} Site aktualisieren
status und primary_domain_id sind bewusst NICHT hierüber setzbar (Publish- bzw. Domain-Flow).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| name | Rumpf | string | nein | |
| slug | Rumpf | string | nein | |
| theme_id | Rumpf | string (uuid) | nein | |
| default_locale | Rumpf | string | nein | |
| locales | Rumpf | array | nein | |
| company | Rumpf | object | nein | |
| branding | Rumpf | object | nein | |
| settings | Rumpf | object | nein |
Anfrage · application/json
{
"name": "<string>",
"slug": "<string>",
"theme_id": "<uuid>",
"default_locale": "<string>",
"locales": [],
"company": {},
"branding": {},
"settings": {}
}Antworten
200{data:Site}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Slug vergeben.
DELETE /sites/{site} Site löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/publish Site veröffentlichen (asynchron)
Reiht einen Publish-Job ein und antwortet sofort. Status via GET /ai/generations/{generation_id}.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| label | Rumpf | string | nein | Optionales Versionslabel. |
| notes | Rumpf | string | nein | Optionale Notiz. |
Anfrage · application/json
{
"label": "<string>",
"notes": "<string>"
}Antworten
202{generation_id, status:"queued"}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:{header, footer}} — je Region eine Seiten-UUID.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
Seiten & Blöcke
Seiten (bilingual) und ihre Inhaltsblöcke.
GET /sites/{site}/pages Seiten einer Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:[Page]} inkl. translations.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/pages Seite anlegen
Bilingual: translations je Locale ({locale,title,slug,…}).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| slug | Rumpf | string | ja | |
| path | Rumpf | string | ja | Beginnt mit "/", nur [a-z0-9/-]. |
| type | Rumpf | string | nein | page|post|landing|legal|system. |
| status | Rumpf | string | nein | draft|published|scheduled|archived. |
| template | Rumpf | string | nein | |
| parent_id | Rumpf | string (uuid) | nein | |
| sort_order | Rumpf | integer | nein | |
| is_home | Rumpf | boolean | nein | |
| is_noindex | Rumpf | boolean | nein | |
| seo | Rumpf | object | nein | |
| translations | Rumpf | array | ja | Pflicht, min. 1 Eintrag mit locale/title/slug. |
Anfrage · 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": []
}Antworten
201{data:Page}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Pfad vergeben / ungültig.
GET /pages/{page} Seite abrufen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| page | Pfad | string | ja | Seiten-UUID. |
Antworten
200{data:Page} inkl. translations, sections, blocks.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
PUT /pages/{page} Seite aktualisieren
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| page | Pfad | string | ja | Seiten-UUID. |
| slug | Rumpf | string | nein | |
| path | Rumpf | string | nein | |
| type | Rumpf | string | nein | |
| status | Rumpf | string | nein | |
| is_home | Rumpf | boolean | nein | |
| is_noindex | Rumpf | boolean | nein | |
| seo | Rumpf | object | nein | |
| published_at | Rumpf | string (date-time) | nein | |
| translations | Rumpf | array | nein | Upsert je Locale. |
Anfrage · application/json
{
"slug": "<string>",
"path": "<string>",
"type": "<string>",
"status": "<string>",
"is_home": false,
"is_noindex": false,
"seo": {},
"published_at": "<date-time>",
"translations": []
}Antworten
200{data:Page}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
DELETE /pages/{page} Seite löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| page | Pfad | string | ja | Seiten-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| page | Pfad | string | ja | Seiten-UUID. |
| blocks | Rumpf | array | ja | IDs oder {id,section_id,sort_order}-Objekte. |
Anfrage · application/json
{
"blocks": []
}Antworten
200{data:[Section]} inkl. sortierter Blöcke.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
POST /pages/{page}/blocks Block hinzufügen
props werden gegen block_types.schema validiert (unbekannte Props -> 422).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| page | Pfad | string | ja | Seiten-UUID. |
| type | Rumpf | string | nein | block_types.key (alternativ block_type_id). |
| block_type_id | Rumpf | string (uuid) | nein | Alternativ zu type. |
| section_id | Rumpf | string (uuid) | nein | Ziel-Section (sonst erste/neue). |
| props | Rumpf | object | nein | Block-Eigenschaften gemäß Schema. |
| content | Rumpf | object | nein | |
| sort_order | Rumpf | integer | nein | |
| is_visible | Rumpf | boolean | nein |
Anfrage · application/json
{
"type": "<string>",
"block_type_id": "<uuid>",
"section_id": "<uuid>",
"props": {},
"content": {},
"sort_order": 0,
"is_visible": false
}Antworten
201{data:Block}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| page | Pfad | string | ja | Seiten-UUID. |
| blocks | Rumpf | array | ja | Pflicht (darf leer sein — leert dann die Seite). Elemente: {id?, type, props?, content?}; type ist ein block_types.key. |
Anfrage · application/json
{
"blocks": []
}Antworten
200{ok:true, blocks:[{id,type}], count}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
PUT /blocks/{block} Block aktualisieren
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| block | Pfad | string | ja | Block-UUID. |
| props | Rumpf | object | nein | |
| content | Rumpf | object | nein | |
| block_type_id | Rumpf | string (uuid) | nein | Optionaler Typwechsel. |
| type | Rumpf | string | nein | |
| section_id | Rumpf | string (uuid) | nein | |
| sort_order | Rumpf | integer | nein | |
| is_visible | Rumpf | boolean | nein |
Anfrage · application/json
{
"props": {},
"content": {},
"block_type_id": "<uuid>",
"type": "<string>",
"section_id": "<uuid>",
"sort_order": 0,
"is_visible": false
}Antworten
200{data:Block}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
DELETE /blocks/{block} Block löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| block | Pfad | string | ja | Block-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
Theme
Design-Tokens je Site (Copy-on-Write für System-Themes).
GET /sites/{site}/theme Theme der Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:Theme} mit tokens.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Kein Theme verfügbar.
PUT /sites/{site}/theme Theme aktualisieren
Copy-on-Write: ein geteiltes System-Theme wird beim ersten Schreiben geklont.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| name | Rumpf | string | nein | |
| tokens | Rumpf | object | ja | Design-Tokens (colors, colorsDark, fonts, gradients, hero, sectionStyle, headerStyle …). |
Anfrage · application/json
{
"name": "<string>",
"tokens": {}
}Antworten
200{data:Theme}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
Medien
Bild-Uploads je Site.
GET /sites/{site}/media Mediathek einer Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| q | Abfrage | string | nein | Volltextsuche über Dateiname, Alt-Text und Titel (case-insensitiv). |
Antworten
200{data:[Asset]} (neueste zuerst) mit url, path, alt, title, width, height, bytes, mime.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/media Bild hochladen
multipart/form-data. Nur Rasterformate (jpeg/png/webp/gif), max. 8 MB; SVG ist bewusst ausgeschlossen.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| file | Rumpf | string (binary) | ja | Bilddatei. |
| alt | Rumpf | string | nein | Optionaler Alt-Text (BFSG). |
Anfrage · multipart/form-data
{
"file": "<binary>",
"alt": "<string>"
}Antworten
201{url, path, bytes, mime, width, height, asset_id}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Ungültige Datei.
PATCH /sites/{site}/media/{asset} Alt-Text/Titel eines Assets setzen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| asset | Pfad | string | ja | Asset-UUID. |
| alt | Rumpf | string | nein | Alt-Text (BFSG). |
| title | Rumpf | string | nein | Optionaler Titel. |
Anfrage · application/json
{
"alt": "<string>",
"title": "<string>"
}Antworten
200{data:Asset}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
DELETE /sites/{site}/media/{asset} Asset löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| asset | Pfad | string | ja | Asset-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| asset | Pfad | string | ja | Asset-UUID. |
Antworten
200{alt, title}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)429Zu viele Anfragen (Rate-Limit)
Block-Typen
Globaler Katalog verfügbarer Blocktypen inkl. Schema.
GET /block-types Block-Typ-Katalog
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| all | Abfrage | boolean | nein | 1 = auch deaktivierte Typen zeigen. |
Antworten
200{data:[BlockType]} mit key, category, schema, default_props, preview_svg.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
CMS
Strukturierte Inhalts-Collections und ihre Einträge je Site.
GET /sites/{site}/collections Collections einer Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:[Collection]} mit name, fields-Schema, Eintragszahl.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/collections Collection anlegen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| name | Rumpf | string | ja | Anzeigename. |
| slug | Rumpf | string | nein | URL-Segment (optional; aus name abgeleitet). |
| fields | Rumpf | array | nein | Feld-Definitionen [{key,label,type}]. |
Anfrage · application/json
{
"name": "<string>",
"slug": "<string>",
"fields": []
}Antworten
201{data:Collection}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
GET /collections/{collection} Collection abrufen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| collection | Pfad | string | ja | Collection-UUID. |
Antworten
200{data:Collection}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
PUT /collections/{collection} Collection ändern
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| collection | Pfad | string | ja | Collection-UUID. |
| name | Rumpf | string | nein | |
| fields | Rumpf | array | nein | Feld-Definitionen. |
Anfrage · application/json
{
"name": "<string>",
"fields": []
}Antworten
200{data:Collection}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
DELETE /collections/{collection} Collection löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| collection | Pfad | string | ja | Collection-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
GET /collections/{collection}/entries Einträge einer Collection
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| collection | Pfad | string | ja | Collection-UUID. |
Antworten
200{data:[Entry]} mit Feldwerten.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /collections/{collection}/entries Eintrag anlegen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| collection | Pfad | string | ja | Collection-UUID. |
| data | Rumpf | object | ja | Feldwerte gemäß Collection-Schema. |
Anfrage · application/json
{
"data": {}
}Antworten
201{data:Entry}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
PUT /entries/{entry} Eintrag ändern
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| entry | Pfad | string | ja | Entry-UUID. |
| data | Rumpf | object | nein | Feldwerte. |
Anfrage · application/json
{
"data": {}
}Antworten
200{data:Entry}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
DELETE /entries/{entry} Eintrag löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| entry | Pfad | string | ja | Entry-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
Versionen
Snapshots einer Site: sichern und wiederherstellen.
GET /sites/{site}/versions Versionsverlauf einer Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:[SiteVersion]} (neueste zuerst) mit label, created_at, Auslöser.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/versions Version sichern (Snapshot)
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| label | Rumpf | string | nein | Optionales Versionslabel. |
Anfrage · application/json
{
"label": "<string>"
}Antworten
201{data:SiteVersion}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| version | Pfad | string | ja | Versions-UUID. |
Antworten
200{ok:true, restored}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:[Activity]} mit type, summary, created_at.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site_id | Rumpf | string | ja | Site-UUID (36 Zeichen). |
| brief | Rumpf | string | ja | Geschäfts-Briefing (3–8000 Zeichen). |
| lang | Rumpf | string | nein | de|en. |
Anfrage · application/json
{
"site_id": "<string>",
"brief": "<string>",
"lang": "<string>"
}Antworten
200{generation_id, blueprint:{sitemap,skeletons}, assistant_text, usage}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Site nicht gefunden.422Validierungsfehler429Zu viele Anfragen (Rate-Limit)
POST /ai/build Site bauen (asynchron)
Reiht einen Build-Job ein (Dutzende KI-Calls) und antwortet sofort. brief ODER blueprint nötig.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site_id | Rumpf | string | ja | |
| brief | Rumpf | string | nein | Alternativ zu blueprint. |
| blueprint | Rumpf | object | nein | Blueprint aus /ai/plan. |
| lang | Rumpf | string | nein | de|en. |
Anfrage · application/json
{
"site_id": "<string>",
"brief": "<string>",
"blueprint": {},
"lang": "<string>"
}Antworten
202{generation_id, status:"queued"}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Weder brief noch blueprint.429Zu viele Anfragen (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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site_id | Rumpf | string | ja | |
| message | Rumpf | string | ja | 1–8000 Zeichen. |
| conversation_id | Rumpf | string | nein | |
| history | Rumpf | array | nein | Bisherige Turns (max. 40). |
| lang | Rumpf | string | nein | de|en. |
| page_id | Rumpf | string | nein | Aktuell geöffnete Seite (Kontext). |
| block_id | Rumpf | string | nein | Aktuell markierter Block (Kontext). |
Anfrage · application/json
{
"site_id": "<string>",
"message": "<string>",
"conversation_id": "<string>",
"history": [],
"lang": "<string>",
"page_id": "<string>",
"block_id": "<string>"
}Antworten
200Server-Sent-Events-Strom (text/event-stream): Events start, delta, tool_call_*, pending_action, done, error401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Chat-Token-Kontingent erschöpft.404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler429Zu viele Anfragen (Rate-Limit)
POST /ai/chat/{pendingAction}/apply Vorgeschlagene Mutation bestätigen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| pendingAction | Pfad | string | ja | ID der Pending-Mutation aus dem Chat-Stream. |
Antworten
200Ergebnis der ausgeführten Aktion.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden / abgelaufen / kein Zugriff.422Validierungsfehler
POST /ai/image Bild generieren (Gemini)
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site_id | Rumpf | string | ja | |
| prompt | Rumpf | string | ja | Bildbeschreibung (3–2000 Zeichen). |
| alt | Rumpf | string | ja | Alt-Text (Pflicht, BFSG). |
| aspect | Rumpf | string | nein | 1:1|4:3|16:9|3:4|9:16 (Default 16:9). |
| premium | Rumpf | boolean | nein | Höhere Qualität (teurer). |
Anfrage · application/json
{
"site_id": "<string>",
"prompt": "<string>",
"alt": "<string>",
"aspect": "<string>",
"premium": false
}Antworten
200Generiertes Asset (url/alt/…).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler429Zu viele Anfragen (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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site_id | Rumpf | string | ja | Site-UUID (36 Zeichen). |
| text | Rumpf | string | ja | Ausgangstext (1–6000 Zeichen). |
| command | Rumpf | string | ja | shorten|lengthen|bullets|professional|simplify|rephrase|fix. |
| lang | Rumpf | string | nein | Zielsprache; ohne Angabe die Standardsprache der Site. · max. 5 Zeichen |
Anfrage · application/json
{
"site_id": "<string>",
"text": "<string>",
"command": "shorten",
"lang": "<string>"
}Antworten
200{text, command, generation_id, usage}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Site 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site_id | Rumpf | string | ja | Site-UUID (36 Zeichen). |
| text | Rumpf | string | nein | Text der Marke; ohne Angabe der Site-Name. · max. 60 Zeichen |
| variant | Rumpf | string | nein | wordmark (Vorgabe) | lockup | monogram. |
| primary | Rumpf | string | nein | Primärfarbe als #rrggbb. · Muster ^#[0-9a-fA-F]{6}$ |
| accent | Rumpf | string | nein | Akzentfarbe als #rrggbb. · Muster ^#[0-9a-fA-F]{6}$ |
| hint | Rumpf | string | nein | Zusätzlicher Gestaltungshinweis. · max. 300 Zeichen |
Anfrage · application/json
{
"site_id": "<string>",
"text": "<string>",
"variant": "wordmark",
"primary": "<string>",
"accent": "<string>",
"hint": "<string>"
}Antworten
200{svg, variant, generation_id, usage}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Site 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| url | Rumpf | string | ja | Öffentliche http(s)-Adresse. · max. 300 Zeichen |
| site_id | Rumpf | string | nein | Optional — ordnet den Lauf einer Site zu (36 Zeichen). |
Anfrage · application/json
{
"url": "<string>",
"site_id": "<string>"
}Antworten
200{brand:{name,tagline,industry,tone,brand_voice,colors:{primary,accent},summary}, source_url, usage}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Site 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| generation | Pfad | string | ja | Generierungs-UUID. |
Antworten
200{id, kind, status, output, error}; status queued|running|succeeded|failed.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| count | Rumpf | integer | nein | Anzahl Vorschläge, 1–8 (Vorgabe 5). |
Anfrage · application/json
{
"count": 1
}Antworten
200{topics:[{title, angle, category, tags}]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler429Rate-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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| topic | Rumpf | string | ja | Thema (3–200 Zeichen). |
| category | Rumpf | string | nein | Optionale Kategorie. · max. 60 Zeichen |
| tags | Rumpf | array | nein | Bis zu 6 Schlagwörter à 40 Zeichen. |
| status | Rumpf | string | nein | draft (Vorgabe) | scheduled | published. |
| publish_at | Rumpf | string (date) | nein | Pflicht bei status=scheduled; frühestens heute. |
Anfrage · application/json
{
"topic": "<string>",
"category": "<string>",
"tags": [],
"status": "draft",
"publish_at": "<date>"
}Antworten
202{generation_id, status:"queued"}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)402Kontingent erschöpft — Upgrade oder Aufladen nötig404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler429Rate-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
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{enabled, settings, knowledge, chat_tokens, embed}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
PUT /sites/{site}/assistant Assistent an/aus + Einstellungen
Aktivieren erfordert mindestens den Starter-Plan (Feature ai_chat).
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| enabled | Rumpf | boolean | nein | |
| settings | Rumpf | object | nein | name, greeting, placeholder, persona, primary, accent, show_branding. |
Anfrage · application/json
{
"enabled": false,
"settings": {}
}Antworten
200{enabled, settings, knowledge, embed}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)403Plan reicht nicht (ai_chat).404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/assistant/reindex RAG-Wissensbasis neu aufbauen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{ok:true, knowledge}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)500Neuaufbau fehlgeschlagen.
Assistent (öffentlich)
Widget-Endpunkte für veröffentlichte Sites (ohne Token).
GET /public/assistant Erreichbarkeits-Probe des Widget-Gates ohne Token
Antworten
200{ok:true, service:"xicflow-assistant"}.
GET /public/assistant/widget.js Eingebettetes Widget-Skript ohne Token
Antworten
200JavaScript (application/javascript).404Skript nicht vorhanden.
GET /public/assistant/{siteSlug}/config Widget-Konfiguration (Branding) ohne Token
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| siteSlug | Pfad | string | ja | Slug der veröffentlichten Site. |
Antworten
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) ohne 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| message | Rumpf | string | ja | Besucherfrage (max. 4000 Zeichen). |
| history | Rumpf | array | nein | |
| conversation_id | Rumpf | string | nein | |
| lang | Rumpf | string | nein | de|en. |
Anfrage · text/plain
{
"message": "<string>",
"history": [],
"conversation_id": "<string>",
"lang": "<string>"
}Antworten
200Server-Sent-Events-Strom (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) ohne 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| siteSlug | Pfad | string | ja | Slug der veröffentlichten Site. |
| name | Rumpf | string | ja | |
| Rumpf | string (email) | ja | ||
| phone | Rumpf | string | nein | |
| message | Rumpf | string | nein | Nachricht ODER url erforderlich. |
| url | Rumpf | string | nein | |
| website | Rumpf | string | nein | Honeypot — leer lassen. |
Anfrage · text/plain
{
"name": "<string>",
"email": "<email>",
"phone": "<string>",
"message": "<string>",
"url": "<string>",
"website": "<string>"
}Antworten
200{ok:true} (immer, auch bei stillem Reject).429Zu viele Anfragen (Rate-Limit)
GET /sites/{site}/submissions Eingänge einer Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:[ContactSubmission]} (neueste zuerst, max. 500).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
PATCH /submissions/{submission} Status setzen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| submission | Pfad | string | ja | Submission-UUID. |
| status | Rumpf | string | ja | new|read|archived. |
Anfrage · application/json
{
"status": "<string>"
}Antworten
200{data:ContactSubmission}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Validierungsfehler
DELETE /submissions/{submission} Eingang löschen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| submission | Pfad | string | ja | Submission-UUID. |
Antworten
200{deleted:true}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200CSV-Datei (Content-Disposition: attachment).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
Domains
Custom-Domains: anmelden, verifizieren, binden.
GET /sites/{site}/domains Domains der Site
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
Antworten
200{data:[Domain]} inkl. dns_instructions.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
POST /sites/{site}/domains Domain anmelden
Preview-Subdomains (*.xicflow.com) werden sofort verifiziert und gebunden.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| site | Pfad | string | ja | Site-UUID. |
| host | Rumpf | string | ja | Reiner Hostname (kein Schema/Pfad). |
| is_primary | Rumpf | boolean | nein | |
| redirect_to | Rumpf | string | nein | Optionales Redirect-Ziel. |
Anfrage · application/json
{
"host": "<string>",
"is_primary": false,
"redirect_to": "<string>"
}Antworten
201{data:Domain, bind}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)403Custom-Domain-Limit erreicht.404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)422Host bereits registriert / ungültig.
POST /domains/{domain}/verify Domain-Besitz verifizieren (TXT)
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| domain | Pfad | string | ja | Domain-UUID. |
Antworten
200{verified:true, data:Domain}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| domain | Pfad | string | ja | Domain-UUID. |
Antworten
200{ok:true, state:"live", steps}.202Angenommen — läuft weiter (DNS noch nicht aufgelöst / manueller Schritt).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)409Noch nicht verifiziert.422Fehlerzustand.
DELETE /domains/{domain} Domain entfernen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| domain | Pfad | string | ja | Domain-UUID. |
Antworten
200{ok:true, detach}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)404Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)
Nutzung
KI-Kontingente und Verbrauch des Kontos.
GET /usage KI-Kontingente & Verbrauch
Antworten
200{plan, quotas, used, remaining, ai_credits, chat_tokens} (-1 = unbegrenzt).401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
Billing
Stripe-Abos, Credit-/Token-Pakete, Add-ons, Rechnungen, Webhook.
GET /billing/config Stripe-Public-Key & aktueller Plan
Antworten
200{publicKey, mode, currentPlan, hasSubscription, prices}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
GET /billing/plans Kaufbarer Katalog
Abo-Tarife (Starter/Pro/Premium/Agentur), Credit- und Chat-Token-Pakete. Preise NETTO in Cent + EUR.
Antworten
200{currentPlan, currency, plans, creditPacks, creditBalance, chatTokenPacks, chatTokens}.401Nicht authentifiziert (fehlendes oder ungültiges 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| target | Rumpf | string | nein | plan|credits|chat_tokens. |
| plan | Rumpf | string | nein | starter|pro|premium|agency (bei target=plan). |
| interval | Rumpf | string | nein | month|year (bei target=plan). |
| pack | Rumpf | string | nein | Pack-Key (bei credits/chat_tokens). |
Anfrage · application/json
{
"target": "<string>",
"plan": "<string>",
"interval": "<string>",
"pack": "<string>"
}Antworten
200{sessionId, url} oder {switched:true, plan} bei In-Place-Upgrade.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Kein Tenant / unbekanntes Paket / ungültige Eingabe.
POST /billing/portal Stripe-Kundenportal
Antworten
200{url}.400Kein Stripe-Konto.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
GET /billing/subscription Aktuelle Abo-Details
Antworten
200{active, status, plan, current_period_end, cancel_at_period_end, next_charge_amount, interval …}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
GET /billing/invoices Lokale GoBD-Rechnungen
Antworten
200{invoices:[…]}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
GET /billing/invoices/{invoiceId}/pdf Rechnung als PDF herunterladen
Nur Rechnungen des eigenen Kontos. Der Dateiname ist die Rechnungsnummer.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| invoiceId | Pfad | string | ja | Rechnungs-UUID aus GET /billing/invoices. |
Antworten
200PDF-Datei (application/pdf, Content-Disposition: attachment).401Nicht authentifiziert (fehlendes oder ungültiges 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.
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| immediate | Abfrage | boolean | nein | true = sofort statt zum Periodenende. |
Antworten
200{ok:true, status, cancel_at_period_end, current_period_end}.400Kein Tenant / keine Subscription.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)500Serverfehler
POST /billing/resume Geplante Kündigung zurücknehmen
Antworten
200{ok:true, cancel_at_period_end:false}.400Keine Subscription.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)500Serverfehler
GET /billing/addons Add-on-Katalog
Antworten
200{addons}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)
POST /billing/addons/checkout Add-ons buchen
Parameter
| Name | Ort | Typ | Pflicht | Hinweis |
|---|---|---|---|---|
| addons | Rumpf | array | ja | Liste {key, quantity?} (extra_site, extra_seat, custom_domain, ki_generations, image_credit_pack). |
Anfrage · application/json
{
"addons": []
}Antworten
200{sessionId, url}.401Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)422Kein Tenant / unbekanntes Add-on.
POST /webhooks/stripe Stripe-Webhook (öffentlich, signaturverifiziert) ohne Token
Kein Bearer-Token. Signatur über Stripe-Signature-Header; Idempotenz via stripe_events. Roh-Body erforderlich.
Antworten
200{received:true}.400Signatur fehlt/ungültig.500Kein Webhook-Secret konfiguriert.
Webhook für neue Anfragen
XICflow ruft von sich aus eine Adresse Ihrer Wahl auf, sobald auf einer veröffentlichten Website ein Formular abgeschickt wurde.
Die Zieladresse tragen Sie je Website im Feld „Automation-Webhook (URL)“ ein; nur https-Adressen werden angenommen. Der Aufruf geschieht nebenläufig mit fünf Sekunden Zeitlimit — antwortet der Empfänger nicht, geht die Anfrage trotzdem nicht verloren, sie steht in jedem Fall im Anfragen-Postfach. Eine automatische Wiederholung gibt es nicht.
Ist ein Signatur-Geheimnis hinterlegt, trägt jeder Aufruf die Kopfzeilen „X-XICflow-Timestamp“ und „X-XICflow-Signature“. Der Empfänger bildet dazu HMAC-SHA256 über den Zeitstempel, einen Punkt und den unveränderten Rumpf und vergleicht das Ergebnis mit dem Wert hinter „sha256=“. Der Zeitstempel schützt davor, dass ein mitgeschnittener Aufruf später erneut eingespielt wird.
Aufbau des Aufrufs
POST
· Ereignis form.submission
| Feld | Typ | Hinweis |
|---|---|---|
| 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>"
}
}Maschinenlesbare Fassung
Dieselbe Beschreibung als OpenAPI 3.1 — für Werkzeuge, die daraus Aufrufe, Typen oder eine eigene Referenz erzeugen.
Die Spezifikation ist frei abrufbar und braucht kein Token. Verbindlich ist immer die Fassung der Schnittstelle selbst; die Kopie neben dieser Dokumentation entsteht beim Bau der Seite aus derselben Quelle.
https://api.xicflow.com/api/openapi.json- Spezifikation der Schnittstelle — immer der aktuelle Stand.
- Kopie neben dieser Dokumentation — Stand des letzten Baus dieser Seite.
- Referenz der Schnittstelle als Webseite — aus derselben Spezifikation erzeugt.
Sie können die Datei in ein Werkzeug Ihrer Wahl laden, um Aufrufe auszuprobieren oder passende Datentypen für Ihre Programmiersprache zu erzeugen. Die Anmeldung bleibt dieselbe: ein Bearer-Token aus /auth/login.