{
  "openapi": "3.1.0",
  "info": {
    "title": "XICflow API",
    "version": "1.0.0",
    "summary": "Control-Plane-API des XICflow-Website-Builders.",
    "description": "REST-API der XICflow-Control-Plane (Editor-Backend). Verwaltet Konten, Sites, Seiten, Blöcke, Themes, Medien, die KI-Pipeline (Plan/Build/Chat/Bild), den eingebetteten Site-Assistenten, Custom-Domains, Nutzungs-Kontingente und das Stripe-Billing.\n\nAuthentifizierung: Bearer-Token (Laravel Sanctum). Nach POST /auth/login bzw. POST /auth/register das zurückgegebene `token` als `Authorization: Bearer <token>` senden. Oeffentliche Endpunkte (Login/Registrierung, OAuth-Callback, Passkey-Login, TOTP-Challenge, Stripe-Webhook, Kontaktformular, öffentlicher Site-Assistent sowie diese Dokumentation) benötigen kein Token.\n\nMandantentrennung: Alle Ressourcen (Sites, Seiten, Blöcke, Domains, Generierungen) sind an das Konto (Tenant) des Tokens gebunden. Der Zugriff auf fremde IDs liefert 404.\n\nRollen: Innerhalb eines Kontos gelten die Team-Rollen owner, admin, editor und viewer (absteigend). Lesende Endpunkte stehen allen Rollen offen. Inhaltliche Änderungen (Seiten, Blöcke, Theme, Medien, CMS, KI-Aufrufe) verlangen mindestens editor; das Anlegen und Löschen von Sites und Domains mindestens admin; Checkout, Kundenportal, Kündigung, Fortsetzung und Add-on-Buchung die Rolle owner. Reicht die Rolle nicht, antwortet die API mit 403 und {error:\"forbidden\", required, role}. Die Team-Verwaltung prüft zusätzlich, dass nur Rollen unterhalb der eigenen vergeben oder geändert werden. Der Konto-Eigentümer hat immer die Rolle owner.\n\nIst das Konto wegen offener Rechnungen gesperrt, liefert jeder authentifizierte Endpunkt 403 mit {error:\"account_suspended\"}.\n\nNicht Teil dieser Referenz: das Betreiber-Backend (/admin/*, nur für XICTRON), die Betreiber-Cron-Endpunkte (/cron/*) und die Maschinen-Endpunkte des Edge-Proxys (/edge/*). Sie sind an einen Betreiber-Schlüssel bzw. an eine hinterlegte Betreiber-Adresse gebunden und für Konten nicht nutzbar.",
    "contact": {
      "name": "XICflow / XICTRON",
      "url": "https://www.xicflow.com",
      "email": "mail@xictron.com"
    }
  },
  "servers": [
    {
      "url": "https://api.xicflow.com/api",
      "description": "Produktion"
    }
  ],
  "tags": [
    {
      "name": "Auth",
      "description": "Registrierung, Login, Sitzungen, 2FA/TOTP, Passkeys, OAuth."
    },
    {
      "name": "Konto",
      "description": "Eigenes Profil, Oberflächensprache und Datenexport (DSGVO Art. 20)."
    },
    {
      "name": "Team",
      "description": "Mitglieder, Rollen, Einladungen und Sitz-Kontingent eines Kontos."
    },
    {
      "name": "Sites",
      "description": "CRUD der Websites eines Kontos plus Veröffentlichung."
    },
    {
      "name": "Seiten & Blöcke",
      "description": "Seiten (bilingual) und ihre Inhaltsblöcke."
    },
    {
      "name": "Theme",
      "description": "Design-Tokens je Site (Copy-on-Write für System-Themes)."
    },
    {
      "name": "Medien",
      "description": "Bild-Uploads je Site."
    },
    {
      "name": "Block-Typen",
      "description": "Globaler Katalog verfügbarer Blocktypen inkl. Schema."
    },
    {
      "name": "CMS",
      "description": "Strukturierte Inhalts-Collections und ihre Einträge je Site."
    },
    {
      "name": "Versionen",
      "description": "Snapshots einer Site: sichern und wiederherstellen."
    },
    {
      "name": "Aktivität",
      "description": "Änderungsverlauf einer Site (KI-Läufe, Versionen, Hand-Edits)."
    },
    {
      "name": "KI-Pipeline",
      "description": "KI-gestütztes Planen, Bauen, Chatten, Text-Überarbeitung, Bild- und Logo-Erzeugung."
    },
    {
      "name": "Blog",
      "description": "KI-Blog je Site: Themenvorschläge und Beitrags-Erzeugung."
    },
    {
      "name": "Assistent",
      "description": "Verwaltung des eingebetteten Site-Assistenten (RAG)."
    },
    {
      "name": "Assistent (öffentlich)",
      "description": "Widget-Endpunkte für veröffentlichte Sites (ohne Token)."
    },
    {
      "name": "Kontaktanfragen",
      "description": "Formular-Eingänge veröffentlichter Sites."
    },
    {
      "name": "Domains",
      "description": "Custom-Domains: anmelden, verifizieren, binden."
    },
    {
      "name": "Nutzung",
      "description": "KI-Kontingente und Verbrauch des Kontos."
    },
    {
      "name": "Billing",
      "description": "Stripe-Abos, Credit-/Token-Pakete, Add-ons, Rechnungen, Webhook."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Sanctum",
        "description": "Sanctum-Personal-Access-Token aus /auth/login oder /auth/register."
      }
    }
  },
  "paths": {
    "/auth/register": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Konto registrieren",
        "description": "Legt Nutzer + eigenen Tenant (Owner) an und liefert ein Bearer-Token. 14-Tage-Trial.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "E-Mail (wird normalisiert, +alias entfernt).",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "description": "Mindestens 10 Zeichen.",
                    "minLength": 10
                  },
                  "name": {
                    "type": "string",
                    "description": "Anzeigename."
                  }
                },
                "required": [
                  "email",
                  "password",
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token + Nutzerprofil ({token, access_token, token_type, user})."
          },
          "422": {
            "description": "E-Mail bereits vergeben oder ungültige Eingabe."
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Login (Passwort, optional 2FA)",
        "description": "Liefert {token, user}. Bei aktivem TOTP ohne totp_code stattdessen {totp_required:true, challenge_token, expires_in}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  },
                  "totp_code": {
                    "type": "string",
                    "description": "Optionaler 6-stelliger TOTP- oder Backup-Code."
                  }
                },
                "required": [
                  "email",
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{token,user} oder {totp_required,challenge_token,expires_in}."
          },
          "401": {
            "description": "Ungültige Zugangsdaten oder 2FA-Code."
          },
          "403": {
            "description": "Konto gesperrt."
          },
          "429": {
            "description": "Zu viele 2FA-Fehlversuche."
          }
        }
      }
    },
    "/auth/totp/challenge": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "2FA per Challenge-Token einlösen",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "challenge_token": {
                    "type": "string",
                    "description": "48-stelliges Token aus der Login-Antwort."
                  },
                  "code": {
                    "type": "string",
                    "description": "TOTP- oder Backup-Code."
                  }
                },
                "required": [
                  "challenge_token",
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{token,user}."
          },
          "401": {
            "description": "Ungültig/abgelaufen."
          },
          "429": {
            "description": "Zu viele Versuche."
          }
        }
      }
    },
    "/auth/passkey/login/options": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkey-Login: Optionen anfordern",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Optional — schränkt allowCredentials ein.",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WebAuthn-publicKey-Request-Optionen (flach)."
          }
        }
      }
    },
    "/auth/passkey/login/verify": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkey-Login: Antwort verifizieren",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Credential-ID (base64url)."
                  },
                  "response": {
                    "type": "object",
                    "description": "WebAuthn-Assertion: clientDataJSON, authenticatorData, signature."
                  }
                },
                "required": [
                  "id",
                  "response"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{token,user}."
          },
          "401": {
            "description": "Challenge abgelaufen / Passkey unbekannt."
          },
          "403": {
            "description": "Konto gesperrt."
          }
        }
      }
    },
    "/auth/oauth/{provider}/redirect": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Google-SSO starten (302)",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "OAuth-Provider, aktuell nur \"google\"."
          },
          {
            "name": "return",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Rückkehr-URL nach erfolgreichem Login."
          }
        ],
        "responses": {
          "302": {
            "description": "Weiterleitung (Browser-Navigation)"
          },
          "404": {
            "description": "Unbekannter Provider."
          }
        }
      }
    },
    "/auth/oauth/{provider}/callback": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Google-SSO-Callback (302 mit Token-Fragment)",
        "description": "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": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "OAuth-Provider, aktuell nur \"google\"."
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "CSRF-State."
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Authorization-Code."
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect ins Frontend mit #token=…&provider=…(&new=1) im Fragment."
          }
        }
      },
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "SSO-Callback als form_post (302, identisch zu GET)",
        "description": "Alias derselben Verarbeitung für Provider mit response_mode=form_post. State und Code werden weiterhin aus der Query gelesen.",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "OAuth-Provider, aktuell nur \"google\"."
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "CSRF-State."
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Authorization-Code."
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect ins Frontend mit #token=… im Fragment."
          }
        }
      }
    },
    "/me": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Aktuelles Nutzerprofil",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Nutzerprofil inkl. tenant (plan/status/trial_ends_at)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Aktuelles Token widerrufen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/auth/change-password": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Passwort ändern",
        "description": "Widerruft alle anderen Sessions. new_password braucht Bestätigung (new_password_confirmation).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "current_password": {
                    "type": "string"
                  },
                  "new_password": {
                    "type": "string",
                    "description": "Mindestens 10 Zeichen, confirmed.",
                    "minLength": 10
                  },
                  "new_password_confirmation": {
                    "type": "string"
                  }
                },
                "required": [
                  "current_password",
                  "new_password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Aktuelles Passwort falsch."
          }
        }
      }
    },
    "/auth/sessions": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Aktive Sitzungen auflisten",
        "description": "Alle Bearer-Token des angemeldeten Nutzers, zuletzt genutzte zuerst. Die eigene Sitzung ist mit current:true markiert.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{sessions:[{id, device, last_used_at, created_at, current}]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      },
      "delete": {
        "tags": [
          "Auth"
        ],
        "summary": "Alle anderen Sitzungen abmelden",
        "description": "Widerruft jedes Token außer dem aktuell verwendeten.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/auth/sessions/{id}": {
      "delete": {
        "tags": [
          "Auth"
        ],
        "summary": "Einzelne Sitzung abmelden",
        "description": "Nur eigene Sitzungen; eine fremde ID trifft nichts und wird trotzdem mit 200 quittiert.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Sitzungs-ID aus GET /auth/sessions."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/auth/totp/status": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "2FA-Status",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{enabled, enabled_at, has_pending_secret, backup_codes_left}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/auth/totp/setup": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "2FA einrichten (Pending-Secret)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{secret, provisioning_uri} (otpauth://)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Bereits aktiviert."
          }
        }
      }
    },
    "/auth/totp/enable": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "2FA aktivieren",
        "description": "Verifiziert den ersten Code und liefert die Backup-Codes EINMALIG im Klartext.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "Aktueller TOTP-Code."
                  }
                },
                "required": [
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{enabled:true, backup_codes[]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Ungültiger Code / kein Pending-Secret."
          }
        }
      }
    },
    "/auth/totp/disable": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "2FA deaktivieren",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "password": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string",
                    "description": "TOTP- oder Backup-Code."
                  }
                },
                "required": [
                  "password",
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{enabled:false}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Passwort oder Code falsch."
          }
        }
      }
    },
    "/auth/totp/regenerate-codes": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Backup-Codes neu erzeugen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "password": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  }
                },
                "required": [
                  "password",
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{backup_codes[]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      }
    },
    "/auth/passkey/register/options": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkey registrieren: Optionen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "WebAuthn-publicKey-Create-Optionen (flach)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/auth/passkey/register/verify": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkey registrieren: verifizieren",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Credential-ID (base64url)."
                  },
                  "response": {
                    "type": "object",
                    "description": "clientDataJSON + attestationObject."
                  },
                  "name": {
                    "type": "string",
                    "description": "Optionaler Anzeigename."
                  }
                },
                "required": [
                  "id",
                  "response"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok:true, id, credential_name}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Challenge/Attestation ungültig."
          }
        }
      }
    },
    "/auth/passkeys": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkeys auflisten",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{passkeys:[{id, credential_name, last_used_at, created_at}]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/auth/passkeys/{id}": {
      "put": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkey umbenennen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Credential-ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "delete": {
        "tags": [
          "Auth"
        ],
        "summary": "Passkey löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Credential-ID."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/auth/oauth/identities": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Verknüpfte OAuth-Identitäten",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{providers:[…]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/auth/oauth/{provider}": {
      "delete": {
        "tags": [
          "Auth"
        ],
        "summary": "OAuth-Identität entkoppeln",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "OAuth-Provider, aktuell nur \"google\"."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Letzter Zugang ohne gesetztes Passwort."
          }
        }
      }
    },
    "/account/profile": {
      "post": {
        "tags": [
          "Konto"
        ],
        "summary": "Eigenen Anzeigenamen ändern",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Anzeigename, max. 190 Zeichen (wird getrimmt).",
                    "maxLength": 190
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{name}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/account/locale": {
      "post": {
        "tags": [
          "Konto"
        ],
        "summary": "Oberflächensprache setzen",
        "description": "Am Konto gespeichert und damit gerätübergreifend wirksam.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "language": {
                    "type": "string",
                    "description": "de oder en.",
                    "enum": [
                      "de",
                      "en"
                    ]
                  }
                },
                "required": [
                  "language"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{language}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/account/data-export": {
      "get": {
        "tags": [
          "Konto"
        ],
        "summary": "Eigene Daten exportieren (DSGVO Art. 20)",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{meta, account, users, sites, contact_submissions, invoices, assets}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Kein Konto (Tenant) am Nutzer hinterlegt."
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/team": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "Mitglieder, Einladungen und Sitze",
        "description": "seats.limit stammt aus dem Tarif; belegte Sitze = aktive Mitglieder plus offene, nicht abgelaufene Einladungen. Bei unbegrenzten Sitzen ist unlimited true und available null.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{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}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Kein Konto (Tenant) am Nutzer hinterlegt."
          }
        }
      }
    },
    "/team/invitations": {
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Mitglied einladen",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Adresse ohne bestehendes XICflow-Konto.",
                    "format": "email",
                    "maxLength": 255
                  },
                  "role": {
                    "type": "string",
                    "description": "admin|editor|viewer.",
                    "enum": [
                      "admin",
                      "editor",
                      "viewer"
                    ]
                  }
                },
                "required": [
                  "email",
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{invitation:{id,email,role,expires_at,expired}, accept_url}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "403": {
            "description": "Rolle reicht nicht (forbidden) oder Zielrolle zu hoch (role_too_high)."
          },
          "422": {
            "description": "already_member, email_taken, seat_limit_reached (mit limit) oder kein Tenant."
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/team/invitations/{invitation}": {
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Offene Einladung widerrufen",
        "description": "Erfordert mindestens die Rolle admin.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "invitation",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Einladungs-UUID aus GET /team."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "403": {
            "description": "Rolle reicht nicht."
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Kein Tenant."
          }
        }
      }
    },
    "/team/members/{member}/role": {
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Rolle eines Mitglieds ändern",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "member",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Nutzer-UUID aus GET /team."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "description": "admin|editor|viewer.",
                    "enum": [
                      "admin",
                      "editor",
                      "viewer"
                    ]
                  }
                },
                "required": [
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok:true, role}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "403": {
            "description": "Rolle reicht nicht."
          },
          "404": {
            "description": "Mitglied nicht im eigenen Konto."
          },
          "422": {
            "description": "cannot_change_owner, cannot_change_self oder kein Tenant."
          }
        }
      }
    },
    "/team/members/{member}": {
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Mitglied entfernen",
        "description": "Entfernt Zugang und Konto des Mitglieds und widerruft dessen Token. Eigentümer und eigenes Konto sind ausgenommen.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "member",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Nutzer-UUID aus GET /team."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "403": {
            "description": "Rolle reicht nicht."
          },
          "404": {
            "description": "Mitglied nicht im eigenen Konto."
          },
          "422": {
            "description": "cannot_remove_owner, cannot_remove_self oder kein Tenant."
          }
        }
      }
    },
    "/public/team/invitation/{token}": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "Einladung vorab ansehen (ohne Anmeldung)",
        "description": "Löst den Einmal-Token auf, damit die Annahme-Seite Team und Rolle anzeigen kann. Rate-Limit 12/min.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Einmal-Token aus dem Einladungslink."
          }
        ],
        "responses": {
          "200": {
            "description": "{email, role, team, expires_at}."
          },
          "404": {
            "description": "Token unbekannt (invalid)."
          },
          "410": {
            "description": "Bereits angenommen (already_accepted) oder abgelaufen (expired)."
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/public/team/accept": {
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Einladung annehmen (ohne Anmeldung)",
        "description": "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.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "Einmal-Token aus dem Einladungslink."
                  },
                  "name": {
                    "type": "string",
                    "description": "Anzeigename, max. 255 Zeichen.",
                    "maxLength": 255
                  },
                  "password": {
                    "type": "string",
                    "description": "Mindestens 10 Zeichen.",
                    "minLength": 10
                  }
                },
                "required": [
                  "token",
                  "name",
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{token, access_token, token_type, user:{id,name,email,role,tenant_id}}."
          },
          "404": {
            "description": "Token unbekannt (invalid)."
          },
          "409": {
            "description": "Für diese E-Mail existiert bereits ein Konto (email_taken)."
          },
          "410": {
            "description": "Bereits angenommen oder abgelaufen."
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/sites": {
      "get": {
        "tags": [
          "Sites"
        ],
        "summary": "Sites des Kontos auflisten",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Site]} inkl. theme, domains, pages_count."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      },
      "post": {
        "tags": [
          "Sites"
        ],
        "summary": "Site anlegen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Kleinbuchstaben/Ziffern/Bindestrich, je Konto eindeutig."
                  },
                  "theme_id": {
                    "type": "string",
                    "description": "Optionales Theme (UUID, eigen oder System).",
                    "format": "uuid"
                  },
                  "default_locale": {
                    "type": "string",
                    "description": "Default \"de\"."
                  },
                  "locales": {
                    "type": "array",
                    "description": "Aktive Sprachen, z. B. [\"de\",\"en\"]."
                  },
                  "company": {
                    "type": "object",
                    "description": "Firmenstammdaten."
                  },
                  "branding": {
                    "type": "object"
                  },
                  "trust_bar": {
                    "type": "object"
                  },
                  "xictraq_id": {
                    "type": "string",
                    "description": "XICTRAQ-Property für Analytics/Monitoring."
                  },
                  "contact_endpoint": {
                    "type": "string"
                  },
                  "contact_recipient": {
                    "type": "string"
                  },
                  "google_verification": {
                    "type": "string"
                  },
                  "settings": {
                    "type": "object"
                  }
                },
                "required": [
                  "name",
                  "slug"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:Site}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Slug vergeben / ungültig."
          }
        }
      }
    },
    "/sites/{site}": {
      "get": {
        "tags": [
          "Sites"
        ],
        "summary": "Site abrufen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:Site} inkl. pages, theme, domains."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "put": {
        "tags": [
          "Sites"
        ],
        "summary": "Site aktualisieren",
        "description": "status und primary_domain_id sind bewusst NICHT hierüber setzbar (Publish- bzw. Domain-Flow).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "theme_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "default_locale": {
                    "type": "string"
                  },
                  "locales": {
                    "type": "array"
                  },
                  "company": {
                    "type": "object"
                  },
                  "branding": {
                    "type": "object"
                  },
                  "settings": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Site}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Slug vergeben."
          }
        }
      },
      "delete": {
        "tags": [
          "Sites"
        ],
        "summary": "Site löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/publish": {
      "post": {
        "tags": [
          "Sites"
        ],
        "summary": "Site veröffentlichen (asynchron)",
        "description": "Reiht einen Publish-Job ein und antwortet sofort. Status via GET /ai/generations/{generation_id}.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "Optionales Versionslabel."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Optionale Notiz."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "{generation_id, status:\"queued\"}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/regions": {
      "get": {
        "tags": [
          "Sites"
        ],
        "summary": "Seiten-IDs der Header-/Footer-Regionen",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:{header, footer}} — je Region eine Seiten-UUID."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/pages": {
      "get": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Seiten einer Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Page]} inkl. translations."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "post": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Seite anlegen",
        "description": "Bilingual: translations je Locale ({locale,title,slug,…}).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Beginnt mit \"/\", nur [a-z0-9/-]."
                  },
                  "type": {
                    "type": "string",
                    "description": "page|post|landing|legal|system."
                  },
                  "status": {
                    "type": "string",
                    "description": "draft|published|scheduled|archived."
                  },
                  "template": {
                    "type": "string"
                  },
                  "parent_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "sort_order": {
                    "type": "integer"
                  },
                  "is_home": {
                    "type": "boolean"
                  },
                  "is_noindex": {
                    "type": "boolean"
                  },
                  "seo": {
                    "type": "object"
                  },
                  "translations": {
                    "type": "array",
                    "description": "Pflicht, min. 1 Eintrag mit locale/title/slug."
                  }
                },
                "required": [
                  "slug",
                  "path",
                  "translations"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:Page}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Pfad vergeben / ungültig."
          }
        }
      }
    },
    "/pages/{page}": {
      "get": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Seite abrufen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Seiten-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:Page} inkl. translations, sections, blocks."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "put": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Seite aktualisieren",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Seiten-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "is_home": {
                    "type": "boolean"
                  },
                  "is_noindex": {
                    "type": "boolean"
                  },
                  "seo": {
                    "type": "object"
                  },
                  "published_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "translations": {
                    "type": "array",
                    "description": "Upsert je Locale."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Page}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "delete": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Seite löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Seiten-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/pages/{page}/reorder-blocks": {
      "put": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Blöcke neu sortieren",
        "description": "blocks: Liste von Block-IDs (Position = sort_order) oder Objekten {id,section_id,sort_order} für sektionsübergreifendes Verschieben.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Seiten-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "blocks": {
                    "type": "array",
                    "description": "IDs oder {id,section_id,sort_order}-Objekte."
                  }
                },
                "required": [
                  "blocks"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:[Section]} inkl. sortierter Blöcke."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      }
    },
    "/pages/{page}/blocks": {
      "put": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Alle Blöcke einer Seite ersetzen (Autosave)",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Seiten-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "blocks": {
                    "type": "array",
                    "description": "Pflicht (darf leer sein — leert dann die Seite). Elemente: {id?, type, props?, content?}; type ist ein block_types.key."
                  }
                },
                "required": [
                  "blocks"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok:true, blocks:[{id,type}], count}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "post": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Block hinzufügen",
        "description": "props werden gegen block_types.schema validiert (unbekannte Props -> 422).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Seiten-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "block_types.key (alternativ block_type_id)."
                  },
                  "block_type_id": {
                    "type": "string",
                    "description": "Alternativ zu type.",
                    "format": "uuid"
                  },
                  "section_id": {
                    "type": "string",
                    "description": "Ziel-Section (sonst erste/neue).",
                    "format": "uuid"
                  },
                  "props": {
                    "type": "object",
                    "description": "Block-Eigenschaften gemäß Schema."
                  },
                  "content": {
                    "type": "object"
                  },
                  "sort_order": {
                    "type": "integer"
                  },
                  "is_visible": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:Block}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Unbekannter Typ / Props verletzen das Schema."
          }
        }
      }
    },
    "/blocks/{block}": {
      "put": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Block aktualisieren",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "block",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Block-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "props": {
                    "type": "object"
                  },
                  "content": {
                    "type": "object"
                  },
                  "block_type_id": {
                    "type": "string",
                    "description": "Optionaler Typwechsel.",
                    "format": "uuid"
                  },
                  "type": {
                    "type": "string"
                  },
                  "section_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "sort_order": {
                    "type": "integer"
                  },
                  "is_visible": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Block}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "delete": {
        "tags": [
          "Seiten & Blöcke"
        ],
        "summary": "Block löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "block",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Block-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/theme": {
      "get": {
        "tags": [
          "Theme"
        ],
        "summary": "Theme der Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:Theme} mit tokens."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Kein Theme verfügbar."
          }
        }
      },
      "put": {
        "tags": [
          "Theme"
        ],
        "summary": "Theme aktualisieren",
        "description": "Copy-on-Write: ein geteiltes System-Theme wird beim ersten Schreiben geklont.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "tokens": {
                    "type": "object",
                    "description": "Design-Tokens (colors, colorsDark, fonts, gradients, hero, sectionStyle, headerStyle …)."
                  }
                },
                "required": [
                  "tokens"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Theme}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      }
    },
    "/sites/{site}/media": {
      "get": {
        "tags": [
          "Medien"
        ],
        "summary": "Mediathek einer Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Volltextsuche über Dateiname, Alt-Text und Titel (case-insensitiv)."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Asset]} (neueste zuerst) mit url, path, alt, title, width, height, bytes, mime."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "post": {
        "tags": [
          "Medien"
        ],
        "summary": "Bild hochladen",
        "description": "multipart/form-data. Nur Rasterformate (jpeg/png/webp/gif), max. 8 MB; SVG ist bewusst ausgeschlossen.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "description": "Bilddatei.",
                    "format": "binary"
                  },
                  "alt": {
                    "type": "string",
                    "description": "Optionaler Alt-Text (BFSG)."
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{url, path, bytes, mime, width, height, asset_id}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Ungültige Datei."
          }
        }
      }
    },
    "/sites/{site}/media/{asset}": {
      "patch": {
        "tags": [
          "Medien"
        ],
        "summary": "Alt-Text/Titel eines Assets setzen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          },
          {
            "name": "asset",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "alt": {
                    "type": "string",
                    "description": "Alt-Text (BFSG)."
                  },
                  "title": {
                    "type": "string",
                    "description": "Optionaler Titel."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Asset}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "delete": {
        "tags": [
          "Medien"
        ],
        "summary": "Asset löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          },
          {
            "name": "asset",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/media/{asset}/describe": {
      "post": {
        "tags": [
          "Medien"
        ],
        "summary": "Alt-Text/Titel per KI-Vision erzeugen",
        "description": "Claude-Vision analysiert das Bild und schlägt Alt-Text + Titel vor. Rate-Limit 20/min.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          },
          {
            "name": "asset",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{alt, title}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/block-types": {
      "get": {
        "tags": [
          "Block-Typen"
        ],
        "summary": "Block-Typ-Katalog",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "all",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "1 = auch deaktivierte Typen zeigen."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[BlockType]} mit key, category, schema, default_props, preview_svg."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/sites/{site}/collections": {
      "get": {
        "tags": [
          "CMS"
        ],
        "summary": "Collections einer Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Collection]} mit name, fields-Schema, Eintragszahl."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "post": {
        "tags": [
          "CMS"
        ],
        "summary": "Collection anlegen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Anzeigename."
                  },
                  "slug": {
                    "type": "string",
                    "description": "URL-Segment (optional; aus name abgeleitet)."
                  },
                  "fields": {
                    "type": "array",
                    "description": "Feld-Definitionen [{key,label,type}]."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:Collection}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      }
    },
    "/collections/{collection}": {
      "get": {
        "tags": [
          "CMS"
        ],
        "summary": "Collection abrufen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Collection-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:Collection}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "put": {
        "tags": [
          "CMS"
        ],
        "summary": "Collection ändern",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Collection-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "fields": {
                    "type": "array",
                    "description": "Feld-Definitionen."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Collection}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "delete": {
        "tags": [
          "CMS"
        ],
        "summary": "Collection löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Collection-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/collections/{collection}/entries": {
      "get": {
        "tags": [
          "CMS"
        ],
        "summary": "Einträge einer Collection",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Collection-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Entry]} mit Feldwerten."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "post": {
        "tags": [
          "CMS"
        ],
        "summary": "Eintrag anlegen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Collection-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "object",
                    "description": "Feldwerte gemäß Collection-Schema."
                  }
                },
                "required": [
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:Entry}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      }
    },
    "/entries/{entry}": {
      "put": {
        "tags": [
          "CMS"
        ],
        "summary": "Eintrag ändern",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entry",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Entry-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "object",
                    "description": "Feldwerte."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:Entry}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "delete": {
        "tags": [
          "CMS"
        ],
        "summary": "Eintrag löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entry",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Entry-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/versions": {
      "get": {
        "tags": [
          "Versionen"
        ],
        "summary": "Versionsverlauf einer Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[SiteVersion]} (neueste zuerst) mit label, created_at, Auslöser."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "post": {
        "tags": [
          "Versionen"
        ],
        "summary": "Version sichern (Snapshot)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "Optionales Versionslabel."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:SiteVersion}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/versions/{version}/restore": {
      "post": {
        "tags": [
          "Versionen"
        ],
        "summary": "Version wiederherstellen",
        "description": "Setzt die Site auf den Snapshot zurück. Der aktuelle Stand wird vorher automatisch als Version gesichert.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Versions-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true, restored}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/activity": {
      "get": {
        "tags": [
          "Aktivität"
        ],
        "summary": "Änderungsverlauf einer Site",
        "description": "Zusammengeführter Feed aus KI-Läufen, gesicherten Versionen und Hand-Edits (neueste zuerst).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Activity]} mit type, summary, created_at."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/ai/plan": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Blueprint planen (Sitemap + Skeletons)",
        "description": "Zwei-Phasen-Plan (nur lesend, keine Writes). Zusätzlich auf 30/min pro Nutzer gedrosselt.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string",
                    "description": "Site-UUID (36 Zeichen)."
                  },
                  "brief": {
                    "type": "string",
                    "description": "Geschäfts-Briefing (3–8000 Zeichen)."
                  },
                  "lang": {
                    "type": "string",
                    "description": "de|en."
                  }
                },
                "required": [
                  "site_id",
                  "brief"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{generation_id, blueprint:{sitemap,skeletons}, assistant_text, usage}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Site nicht gefunden."
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/ai/build": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Site bauen (asynchron)",
        "description": "Reiht einen Build-Job ein (Dutzende KI-Calls) und antwortet sofort. brief ODER blueprint nötig.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string"
                  },
                  "brief": {
                    "type": "string",
                    "description": "Alternativ zu blueprint."
                  },
                  "blueprint": {
                    "type": "object",
                    "description": "Blueprint aus /ai/plan."
                  },
                  "lang": {
                    "type": "string",
                    "description": "de|en."
                  }
                },
                "required": [
                  "site_id"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "{generation_id, status:\"queued\"}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Weder brief noch blueprint."
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/ai/chat": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Editor-Assistent (SSE-Streaming)",
        "description": "Interaktives Editieren via Tool-Calls. Antwort ist ein text/event-stream; Mutationen werden ggf. als pending_action zur Bestätigung ausgegeben.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string",
                    "description": "1–8000 Zeichen."
                  },
                  "conversation_id": {
                    "type": "string"
                  },
                  "history": {
                    "type": "array",
                    "description": "Bisherige Turns (max. 40)."
                  },
                  "lang": {
                    "type": "string",
                    "description": "de|en."
                  },
                  "page_id": {
                    "type": "string",
                    "description": "Aktuell geöffnete Seite (Kontext)."
                  },
                  "block_id": {
                    "type": "string",
                    "description": "Aktuell markierter Block (Kontext)."
                  }
                },
                "required": [
                  "site_id",
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-Sent-Events-Strom (text/event-stream): Events start, delta, tool_call_*, pending_action, done, error"
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Chat-Token-Kontingent erschöpft."
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/ai/chat/{pendingAction}/apply": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Vorgeschlagene Mutation bestätigen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "pendingAction",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID der Pending-Mutation aus dem Chat-Stream."
          }
        ],
        "responses": {
          "200": {
            "description": "Ergebnis der ausgeführten Aktion."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden / abgelaufen / kein Zugriff."
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      }
    },
    "/ai/image": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Bild generieren (Gemini)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string"
                  },
                  "prompt": {
                    "type": "string",
                    "description": "Bildbeschreibung (3–2000 Zeichen)."
                  },
                  "alt": {
                    "type": "string",
                    "description": "Alt-Text (Pflicht, BFSG)."
                  },
                  "aspect": {
                    "type": "string",
                    "description": "1:1|4:3|16:9|3:4|9:16 (Default 16:9)."
                  },
                  "premium": {
                    "type": "boolean",
                    "description": "Höhere Qualität (teurer)."
                  }
                },
                "required": [
                  "site_id",
                  "prompt",
                  "alt"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generiertes Asset (url/alt/…)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/ai/rewrite": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Textbaustein überarbeiten",
        "description": "Ü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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string",
                    "description": "Site-UUID (36 Zeichen)."
                  },
                  "text": {
                    "type": "string",
                    "description": "Ausgangstext (1–6000 Zeichen).",
                    "minLength": 1,
                    "maxLength": 6000
                  },
                  "command": {
                    "type": "string",
                    "description": "shorten|lengthen|bullets|professional|simplify|rephrase|fix.",
                    "enum": [
                      "shorten",
                      "lengthen",
                      "bullets",
                      "professional",
                      "simplify",
                      "rephrase",
                      "fix"
                    ]
                  },
                  "lang": {
                    "type": "string",
                    "description": "Zielsprache; ohne Angabe die Standardsprache der Site.",
                    "maxLength": 5
                  }
                },
                "required": [
                  "site_id",
                  "text",
                  "command"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{text, command, generation_id, usage}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Site nicht gefunden."
          },
          "422": {
            "description": "Ungültige Eingabe oder kein verwertbares Ergebnis."
          },
          "429": {
            "description": "Rate-Limit oder Kontingentsperre belegt (busy)."
          },
          "503": {
            "description": "Textveredelung derzeit nicht verfügbar."
          }
        }
      }
    },
    "/ai/logo": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Logo als SVG erzeugen",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string",
                    "description": "Site-UUID (36 Zeichen)."
                  },
                  "text": {
                    "type": "string",
                    "description": "Text der Marke; ohne Angabe der Site-Name.",
                    "maxLength": 60
                  },
                  "variant": {
                    "type": "string",
                    "description": "wordmark (Vorgabe) | lockup | monogram.",
                    "enum": [
                      "wordmark",
                      "lockup",
                      "monogram"
                    ]
                  },
                  "primary": {
                    "type": "string",
                    "description": "Primärfarbe als #rrggbb.",
                    "pattern": "^#[0-9a-fA-F]{6}$"
                  },
                  "accent": {
                    "type": "string",
                    "description": "Akzentfarbe als #rrggbb.",
                    "pattern": "^#[0-9a-fA-F]{6}$"
                  },
                  "hint": {
                    "type": "string",
                    "description": "Zusätzlicher Gestaltungshinweis.",
                    "maxLength": 300
                  }
                },
                "required": [
                  "site_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{svg, variant, generation_id, usage}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Site nicht gefunden."
          },
          "422": {
            "description": "Ungültige Eingabe oder kein verwertbares SVG erhalten."
          },
          "429": {
            "description": "Rate-Limit oder Kontingentsperre belegt (busy)."
          },
          "503": {
            "description": "Logo-Erzeugung derzeit nicht verfügbar."
          }
        }
      }
    },
    "/ai/brand-extract": {
      "post": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Markenvorschlag aus bestehender Website",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Öffentliche http(s)-Adresse.",
                    "maxLength": 300
                  },
                  "site_id": {
                    "type": "string",
                    "description": "Optional — ordnet den Lauf einer Site zu (36 Zeichen)."
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{brand:{name,tagline,industry,tone,brand_voice,colors:{primary,accent},summary}, source_url, usage}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Site nicht gefunden (nur bei gesetzter site_id)."
          },
          "422": {
            "description": "Adresse ungültig/nicht erreichbar oder kein verwertbares Ergebnis."
          },
          "429": {
            "description": "Rate-Limit oder Kontingentsperre belegt (busy)."
          },
          "503": {
            "description": "Marken-Analyse derzeit nicht verfügbar."
          }
        }
      }
    },
    "/ai/generations/{generation}": {
      "get": {
        "tags": [
          "KI-Pipeline"
        ],
        "summary": "Status einer Generierung (Polling)",
        "description": "Fortschritt von Build/Publish. Großzügiger Limiter (120/min) für sekündliches Polling.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "generation",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Generierungs-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{id, kind, status, output, error}; status queued|running|succeeded|failed."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/blog/topics": {
      "post": {
        "tags": [
          "Blog"
        ],
        "summary": "Themenvorschläge für den Blog",
        "description": "Synchron. Verbraucht eine ai_text-Einheit.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer",
                    "description": "Anzahl Vorschläge, 1–8 (Vorgabe 5).",
                    "minimum": 1,
                    "maximum": 8
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{topics:[{title, angle, category, tags}]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Rate-Limit oder Kontingentsperre belegt (busy)."
          },
          "503": {
            "description": "Themenvorschläge derzeit nicht verfügbar."
          }
        }
      }
    },
    "/sites/{site}/blog/generate": {
      "post": {
        "tags": [
          "Blog"
        ],
        "summary": "Blogbeitrag erzeugen (asynchron)",
        "description": "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.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "topic": {
                    "type": "string",
                    "description": "Thema (3–200 Zeichen).",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "category": {
                    "type": "string",
                    "description": "Optionale Kategorie.",
                    "maxLength": 60
                  },
                  "tags": {
                    "type": "array",
                    "description": "Bis zu 6 Schlagwörter à 40 Zeichen.",
                    "maxItems": 6
                  },
                  "status": {
                    "type": "string",
                    "description": "draft (Vorgabe) | scheduled | published.",
                    "enum": [
                      "draft",
                      "scheduled",
                      "published"
                    ]
                  },
                  "publish_at": {
                    "type": "string",
                    "description": "Pflicht bei status=scheduled; frühestens heute.",
                    "format": "date"
                  }
                },
                "required": [
                  "topic"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "{generation_id, status:\"queued\"}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "402": {
            "description": "Kontingent erschöpft — Upgrade oder Aufladen nötig"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          },
          "429": {
            "description": "Rate-Limit oder Kontingentsperre belegt (busy)."
          },
          "500": {
            "description": "Start fehlgeschlagen (dispatch_failed) — verbrauchte Credits werden erstattet."
          }
        }
      }
    },
    "/sites/{site}/assistant": {
      "get": {
        "tags": [
          "Assistent"
        ],
        "summary": "Assistent-Status & Einstellungen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{enabled, settings, knowledge, chat_tokens, embed}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "put": {
        "tags": [
          "Assistent"
        ],
        "summary": "Assistent an/aus + Einstellungen",
        "description": "Aktivieren erfordert mindestens den Starter-Plan (Feature ai_chat).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "settings": {
                    "type": "object",
                    "description": "name, greeting, placeholder, persona, primary, accent, show_branding."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{enabled, settings, knowledge, embed}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "403": {
            "description": "Plan reicht nicht (ai_chat)."
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/assistant/reindex": {
      "post": {
        "tags": [
          "Assistent"
        ],
        "summary": "RAG-Wissensbasis neu aufbauen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true, knowledge}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "500": {
            "description": "Neuaufbau fehlgeschlagen."
          }
        }
      }
    },
    "/public/assistant": {
      "get": {
        "tags": [
          "Assistent (öffentlich)"
        ],
        "summary": "Erreichbarkeits-Probe des Widget-Gates",
        "responses": {
          "200": {
            "description": "{ok:true, service:\"xicflow-assistant\"}."
          }
        }
      }
    },
    "/public/assistant/widget.js": {
      "get": {
        "tags": [
          "Assistent (öffentlich)"
        ],
        "summary": "Eingebettetes Widget-Skript",
        "responses": {
          "200": {
            "description": "JavaScript (application/javascript)."
          },
          "404": {
            "description": "Skript nicht vorhanden."
          }
        }
      }
    },
    "/public/assistant/{siteSlug}/config": {
      "get": {
        "tags": [
          "Assistent (öffentlich)"
        ],
        "summary": "Widget-Konfiguration (Branding)",
        "parameters": [
          {
            "name": "siteSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Slug der veröffentlichten Site."
          }
        ],
        "responses": {
          "200": {
            "description": "{enabled, name, greeting, placeholder, locale, colors, branding} (enabled:false wenn nicht freigeschaltet)."
          }
        }
      }
    },
    "/public/assistant/{siteSlug}/chat": {
      "post": {
        "tags": [
          "Assistent (öffentlich)"
        ],
        "summary": "Geerdete Q&A des Site-Assistenten (SSE)",
        "description": "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.",
        "requestBody": {
          "required": true,
          "content": {
            "text/plain": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "Besucherfrage (max. 4000 Zeichen)."
                  },
                  "history": {
                    "type": "array"
                  },
                  "conversation_id": {
                    "type": "string"
                  },
                  "lang": {
                    "type": "string",
                    "description": "de|en."
                  }
                },
                "required": [
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-Sent-Events-Strom (text/event-stream): Events start, delta, tool_call_*, pending_action, done, error"
          },
          "404": {
            "description": "Assistent nicht verfügbar."
          },
          "422": {
            "description": "Leere/zu lange Nachricht."
          }
        }
      }
    },
    "/public/contact/{siteSlug}": {
      "post": {
        "tags": [
          "Kontaktanfragen"
        ],
        "summary": "Formular-Eingang (öffentlich)",
        "description": "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": "siteSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Slug der veröffentlichten Site."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "text/plain": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string",
                    "description": "Nachricht ODER url erforderlich."
                  },
                  "url": {
                    "type": "string"
                  },
                  "website": {
                    "type": "string",
                    "description": "Honeypot — leer lassen."
                  }
                },
                "required": [
                  "name",
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok:true} (immer, auch bei stillem Reject)."
          },
          "429": {
            "description": "Zu viele Anfragen (Rate-Limit)"
          }
        }
      }
    },
    "/sites/{site}/submissions": {
      "get": {
        "tags": [
          "Kontaktanfragen"
        ],
        "summary": "Eingänge einer Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[ContactSubmission]} (neueste zuerst, max. 500)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/submissions/{submission}": {
      "patch": {
        "tags": [
          "Kontaktanfragen"
        ],
        "summary": "Status setzen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "submission",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Submission-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "new|read|archived."
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{data:ContactSubmission}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Validierungsfehler"
          }
        }
      },
      "delete": {
        "tags": [
          "Kontaktanfragen"
        ],
        "summary": "Eingang löschen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "submission",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Submission-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted:true}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/submissions/export": {
      "get": {
        "tags": [
          "Kontaktanfragen"
        ],
        "summary": "Eingänge als CSV exportieren",
        "description": "text/csv (Semikolon-Delimiter + UTF-8-BOM, DE-Excel-tauglich). Custom-Formularfelder werden als zusätzliche Spalten ergänzt.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "CSV-Datei (Content-Disposition: attachment)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/sites/{site}/domains": {
      "get": {
        "tags": [
          "Domains"
        ],
        "summary": "Domains der Site",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{data:[Domain]} inkl. dns_instructions."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      },
      "post": {
        "tags": [
          "Domains"
        ],
        "summary": "Domain anmelden",
        "description": "Preview-Subdomains (*.xicflow.com) werden sofort verifiziert und gebunden.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "site",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Site-UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "host": {
                    "type": "string",
                    "description": "Reiner Hostname (kein Schema/Pfad)."
                  },
                  "is_primary": {
                    "type": "boolean"
                  },
                  "redirect_to": {
                    "type": "string",
                    "description": "Optionales Redirect-Ziel."
                  }
                },
                "required": [
                  "host"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{data:Domain, bind}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "403": {
            "description": "Custom-Domain-Limit erreicht."
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Host bereits registriert / ungültig."
          }
        }
      }
    },
    "/domains/{domain}/verify": {
      "post": {
        "tags": [
          "Domains"
        ],
        "summary": "Domain-Besitz verifizieren (TXT)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Domain-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{verified:true, data:Domain}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "422": {
            "description": "Noch nicht verifiziert (reason/checked)."
          }
        }
      }
    },
    "/domains/{domain}/bind": {
      "post": {
        "tags": [
          "Domains"
        ],
        "summary": "Domain binden (bis live)",
        "description": "Fährt die State-Machine (Plesk-Alias + LE + nginx-Map). Vorbedingung: verifiziert.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Domain-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true, state:\"live\", steps}."
          },
          "202": {
            "description": "Angenommen — läuft weiter (DNS noch nicht aufgelöst / manueller Schritt)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          },
          "409": {
            "description": "Noch nicht verifiziert."
          },
          "422": {
            "description": "Fehlerzustand."
          }
        }
      }
    },
    "/domains/{domain}": {
      "delete": {
        "tags": [
          "Domains"
        ],
        "summary": "Domain entfernen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Domain-UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true, detach}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Nicht gefunden (nicht vorhanden oder Ressource eines fremden Kontos)"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "tags": [
          "Nutzung"
        ],
        "summary": "KI-Kontingente & Verbrauch",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{plan, quotas, used, remaining, ai_credits, chat_tokens} (-1 = unbegrenzt)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/config": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Stripe-Public-Key & aktueller Plan",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{publicKey, mode, currentPlan, hasSubscription, prices}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/plans": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Kaufbarer Katalog",
        "description": "Abo-Tarife (Starter/Pro/Premium/Agentur), Credit- und Chat-Token-Pakete. Preise NETTO in Cent + EUR.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{currentPlan, currency, plans, creditPacks, creditBalance, chatTokenPacks, chatTokens}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/checkout": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Checkout starten (Abo / Credits / Chat-Tokens)",
        "description": "target steuert die Variante: plan (Default, mode=subscription) | credits | chat_tokens (mode=payment). Aktives Abo -> sofortiges In-Place-Upgrade mit Proration.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "target": {
                    "type": "string",
                    "description": "plan|credits|chat_tokens."
                  },
                  "plan": {
                    "type": "string",
                    "description": "starter|pro|premium|agency (bei target=plan)."
                  },
                  "interval": {
                    "type": "string",
                    "description": "month|year (bei target=plan)."
                  },
                  "pack": {
                    "type": "string",
                    "description": "Pack-Key (bei credits/chat_tokens)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{sessionId, url} oder {switched:true, plan} bei In-Place-Upgrade."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Kein Tenant / unbekanntes Paket / ungültige Eingabe."
          }
        }
      }
    },
    "/billing/portal": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Stripe-Kundenportal",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{url}."
          },
          "400": {
            "description": "Kein Stripe-Konto."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/subscription": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Aktuelle Abo-Details",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{active, status, plan, current_period_end, cancel_at_period_end, next_charge_amount, interval …}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/invoices": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Lokale GoBD-Rechnungen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{invoices:[…]}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/invoices/{invoiceId}/pdf": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Rechnung als PDF herunterladen",
        "description": "Nur Rechnungen des eigenen Kontos. Der Dateiname ist die Rechnungsnummer.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Rechnungs-UUID aus GET /billing/invoices."
          }
        ],
        "responses": {
          "200": {
            "description": "PDF-Datei (application/pdf, Content-Disposition: attachment)."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "404": {
            "description": "Rechnung nicht gefunden, ohne PDF oder Datei nicht vorhanden (file_missing)."
          }
        }
      }
    },
    "/billing/cancel": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Abo kündigen",
        "description": "Standard zum Periodenende; ?immediate=true kündigt sofort.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "immediate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "true = sofort statt zum Periodenende."
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true, status, cancel_at_period_end, current_period_end}."
          },
          "400": {
            "description": "Kein Tenant / keine Subscription."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "500": {
            "description": "Serverfehler"
          }
        }
      }
    },
    "/billing/resume": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Geplante Kündigung zurücknehmen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ok:true, cancel_at_period_end:false}."
          },
          "400": {
            "description": "Keine Subscription."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "500": {
            "description": "Serverfehler"
          }
        }
      }
    },
    "/billing/addons": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Add-on-Katalog",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{addons}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          }
        }
      }
    },
    "/billing/addons/checkout": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Add-ons buchen",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "addons": {
                    "type": "array",
                    "description": "Liste {key, quantity?} (extra_site, extra_seat, custom_domain, ki_generations, image_credit_pack)."
                  }
                },
                "required": [
                  "addons"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{sessionId, url}."
          },
          "401": {
            "description": "Nicht authentifiziert (fehlendes oder ungültiges Bearer-Token)"
          },
          "422": {
            "description": "Kein Tenant / unbekanntes Add-on."
          }
        }
      }
    },
    "/webhooks/stripe": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Stripe-Webhook (öffentlich, signaturverifiziert)",
        "description": "Kein Bearer-Token. Signatur über Stripe-Signature-Header; Idempotenz via stripe_events. Roh-Body erforderlich.",
        "responses": {
          "200": {
            "description": "{received:true}."
          },
          "400": {
            "description": "Signatur fehlt/ungültig."
          },
          "500": {
            "description": "Kein Webhook-Secret konfiguriert."
          }
        }
      }
    }
  },
  "webhooks": {
    "form.submission": {
      "post": {
        "tags": [
          "Kontaktanfragen"
        ],
        "summary": "Ausgehend: neuer Formular-Eingang",
        "description": "HTTPS-POST an die je Site gesetzte settings.webhook_url, sobald ein Formular abgeschickt wurde. Non-blocking, 5 s Timeout, ausschließlich öffentliche HTTPS-Ziele (SSRF-Schutz; jeder Redirect-Hop wird erneut geprüft). Ein Fehler des Empfängers verhindert die Lead-Erfassung nicht. SIGNATUR: Ist ein Signatur-Geheimnis gesetzt (automatisch bei Aktivierung erzeugt), trägt jeder Aufruf die Header X-XICflow-Timestamp und X-XICflow-Signature=sha256=<hex>. Der Empfänger bildet HMAC-SHA256 über \"<X-XICflow-Timestamp>.<roher Body>\" mit dem Geheimnis und vergleicht konstant-zeitig; der Timestamp schützt vor Replay.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "form.submission"
                    ]
                  },
                  "site": {
                    "type": "string",
                    "description": "Slug der Site."
                  },
                  "submitted_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "submission": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": "string",
                        "nullable": true
                      },
                      "email": {
                        "type": "string",
                        "nullable": true
                      },
                      "phone": {
                        "type": "string",
                        "nullable": true
                      },
                      "message": {
                        "type": "string",
                        "nullable": true
                      },
                      "url": {
                        "type": "string",
                        "nullable": true
                      },
                      "fields": {
                        "type": "object",
                        "nullable": true,
                        "description": "Zusaetzliche Form-Builder-Felder."
                      },
                      "form": {
                        "type": "string",
                        "nullable": true,
                        "description": "Kennung des ausgeloesten Formulars (aus dem Formular-Meta)."
                      }
                    }
                  }
                },
                "required": [
                  "event",
                  "site",
                  "submitted_at",
                  "submission"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vom Empfänger bestätigt (Antwort wird ignoriert)."
          }
        }
      }
    }
  }
}
