{
  "openapi": "3.1.0",
  "info": {
    "title": "Blck Alpaca Public API",
    "version": "1.0.0",
    "summary": "Öffentliche Endpunkte der Website blckalpaca.at.",
    "description": "Diese Endpunkte bedienen die Formulare und die Suche der Website.\nEs ist keine Produkt-API: keine Authentifizierung, keine Schlüssel,\nkeine Zusicherung zur Verfügbarkeit.\n\nFehlerantworten haben durchgehend dieselbe Form mit `error` (stabiler Code),\n`message` (deutsch, für Menschen) und `hint` (was zu tun ist).\n\nEndpunkte, die einen im Browser erzeugten Challenge-Token verlangen, sind\nals browser-only gekennzeichnet und lassen sich nicht sinnvoll skripten.\n\nKONTINGENTE: Endpunkte mit Rate-Limit liefern `RateLimit` und `RateLimit-Policy`\nnach draft-ietf-httpapi-ratelimit-headers-11 sowie die Legacy-Header\n`RateLimit-Limit`, `RateLimit-Remaining` und `RateLimit-Reset`. Sie stehen auf\nJEDER Antwort dieser Endpunkte, nicht erst auf dem 429er - wer sie liest, muss\nnicht erst gegen die Wand laufen. `RateLimit-Reset` ist ein Delta in Sekunden,\nkein Zeitstempel.\n\nNicht abgebildet ist das Limit von einem SEO-Audit pro E-Mail-Adresse und Woche.\nEs hängt an der Adresse im Body, nicht am Aufrufer; im Header wäre es ein Weg,\nfremde Adressen auf früheren Gebrauch abzufragen.\n\nInterne Endpunkte sind hier bewusst nicht beschrieben.",
    "contact": {
      "name": "Blck Alpaca OG",
      "email": "office@blckalpaca.at",
      "url": "https://blckalpaca.at/de/kontakt"
    },
    "license": {
      "name": "Proprietär",
      "url": "https://blckalpaca.at/de/impressum"
    }
  },
  "servers": [
    {
      "url": "https://blckalpaca.at",
      "description": "Produktion"
    }
  ],
  "tags": [
    {
      "name": "Suche",
      "description": "Lesende Endpunkte ohne Nebenwirkung."
    },
    {
      "name": "Formulare",
      "description": "Schreibende Endpunkte mit Rate-Limit und Spam-Schutz."
    },
    {
      "name": "Double-Opt-In",
      "description": "Token-basierte Bestätigungslinks aus E-Mails."
    },
    {
      "name": "Betrieb",
      "description": "Status der Anwendung."
    }
  ],
  "paths": {
    "/api/knowledge-search": {
      "get": {
        "tags": [
          "Suche"
        ],
        "operationId": "searchKnowledgeBase",
        "summary": "Knowledge-Base durchsuchen",
        "description": "Volltextsuche über Titel, Definition und Hauptkeyword der Knowledge-Base-Artikel. Maximal 8 Treffer. Fehlerfrei per Design: bei leerer Anfrage oder internem Fehler kommt eine leere Trefferliste, kein Fehlerobjekt.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Suchbegriff."
          },
          {
            "name": "locale",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en",
                "sk"
              ],
              "default": "de"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trefferliste, gegebenenfalls leer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "articleNumber": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "definition": {
                            "type": "string",
                            "description": "Auf 120 Zeichen gekürzt."
                          },
                          "difficulty": {
                            "type": "string"
                          },
                          "topicSlug": {
                            "type": "string"
                          },
                          "categorySlug": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "Betrieb"
        ],
        "operationId": "getHealth",
        "summary": "Status der Anwendung",
        "description": "Für Monitoring. `degraded` bedeutet: Redis fehlt, die Anwendung läuft mit In-Memory-Fallback weiter und antwortet weiterhin mit 200.",
        "responses": {
          "200": {
            "description": "healthy oder degraded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "healthy",
                        "degraded"
                      ]
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "services": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "METHOD_NOT_ALLOWED. Nur GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "unhealthy. Body enthält zusätzlich `status: \"unhealthy\"`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "tags": [
          "Formulare"
        ],
        "operationId": "submitContactForm",
        "summary": "Kontaktanfrage senden",
        "description": "Löst eine Double-Opt-In-Bestätigungsmail aus. Die Anfrage wird erst nach Klick auf den Link darin zugestellt. Spam-Schutz und Rate-Limit sind aktiv.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "email",
                  "company",
                  "message"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 100
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "company": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 100
                  },
                  "message": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 5000
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 30
                  },
                  "locale": {
                    "type": "string",
                    "enum": [
                      "de",
                      "en",
                      "sk"
                    ],
                    "default": "de"
                  },
                  "page": {
                    "type": "string",
                    "description": "Pfad, von dem abgesendet wurde. Nur für Auswertung."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Angenommen. Bestätigungsmail wurde versendet.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR, INVALID_EMAIL, INVALID_EMAIL_DOMAIN, DISPOSABLE_EMAIL, SPAM_REJECTED oder MALFORMED_BODY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "405": {
            "description": "METHOD_NOT_ALLOWED. Nur POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/newsletter/subscribe": {
      "post": {
        "tags": [
          "Formulare"
        ],
        "operationId": "subscribeNewsletter",
        "summary": "Newsletter abonnieren",
        "description": "Double-Opt-In. `gdprConsent` ist rechtlich erforderlich, ohne die Einwilligung wird nichts gespeichert.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "gdprConsent"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "gdprConsent": {
                    "type": "boolean",
                    "enum": [
                      true
                    ],
                    "description": "Muss true sein."
                  },
                  "firstName": {
                    "type": "string"
                  },
                  "locale": {
                    "type": "string",
                    "enum": [
                      "de",
                      "en",
                      "sk"
                    ],
                    "default": "de"
                  },
                  "source": {
                    "type": "string",
                    "maxLength": 50,
                    "description": "Woher die Anmeldung kam."
                  },
                  "trackingConsent": {
                    "type": "boolean"
                  },
                  "utm_source": {
                    "type": "string"
                  },
                  "utm_medium": {
                    "type": "string"
                  },
                  "utm_campaign": {
                    "type": "string"
                  },
                  "utm_content": {
                    "type": "string"
                  },
                  "utm_term": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Angenommen. Bestätigungsmail wurde versendet.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "400": {
            "description": "MISSING_EMAIL, GDPR_REQUIRED, INVALID_EMAIL, INVALID_EMAIL_DOMAIN, DISPOSABLE_EMAIL, INVALID_WEBSITE, ALREADY_SUBSCRIBED oder MALFORMED_BODY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "405": {
            "description": "METHOD_NOT_ALLOWED. Nur POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/newsletter/unsubscribe": {
      "post": {
        "tags": [
          "Formulare"
        ],
        "operationId": "unsubscribeNewsletter",
        "summary": "Newsletter abbestellen",
        "description": "Erwartet den Token aus dem Abmeldelink der E-Mail. Optional kann ein Feedback-Grund mitgegeben werden.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "locale": {
                    "type": "string",
                    "enum": [
                      "de",
                      "en",
                      "sk"
                    ],
                    "default": "de"
                  },
                  "feedbackReason": {
                    "type": "string"
                  },
                  "feedbackText": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Abgemeldet."
          },
          "400": {
            "description": "INVALID_TOKEN oder MALFORMED_BODY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/newsletter/unsubscribe/validate": {
      "get": {
        "tags": [
          "Double-Opt-In"
        ],
        "operationId": "validateUnsubscribeToken",
        "summary": "Abmelde-Token prüfen",
        "description": "Prüft, ob ein Abmelde-Token gültig ist, ohne die Abmeldung auszuführen.",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ergebnis der Prüfung."
          }
        }
      }
    },
    "/api/newsletter/verify": {
      "get": {
        "tags": [
          "Double-Opt-In"
        ],
        "operationId": "verifyNewsletterSubscription",
        "summary": "Newsletter-Anmeldung bestätigen",
        "description": "Ziel des Links aus der Bestätigungsmail. Antwortet mit einer Weiterleitung auf eine Statusseite, nicht mit JSON.",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "307": {
            "description": "Weiterleitung auf die Statusseite."
          }
        }
      }
    },
    "/api/contact/verify": {
      "get": {
        "tags": [
          "Double-Opt-In"
        ],
        "operationId": "verifyContactRequest",
        "summary": "Kontaktanfrage bestätigen",
        "description": "Ziel des Links aus der Bestätigungsmail. Erst danach wird die Anfrage zugestellt. Antwortet mit einer Weiterleitung, nicht mit JSON.",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en",
                "sk"
              ],
              "default": "de"
            }
          }
        ],
        "responses": {
          "307": {
            "description": "Weiterleitung auf die Statusseite."
          }
        }
      }
    },
    "/api/chat": {
      "post": {
        "tags": [
          "Formulare"
        ],
        "operationId": "sendChatMessage",
        "summary": "Nachricht an den Website-Assistenten",
        "description": "Rate-Limit pro IP und zusätzlich pro Session. Bei 429 nennt der `Retry-After`-Header die Wartezeit. Es werden zwei Body-Formate akzeptiert.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "title": "Nachrichtenverlauf",
                    "type": "object",
                    "required": [
                      "messages"
                    ],
                    "properties": {
                      "messages": {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                          "type": "object",
                          "required": [
                            "role",
                            "content"
                          ],
                          "properties": {
                            "role": {
                              "type": "string",
                              "enum": [
                                "user",
                                "assistant",
                                "system"
                              ]
                            },
                            "content": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 5000
                            }
                          }
                        }
                      },
                      "locale": {
                        "type": "string",
                        "enum": [
                          "de",
                          "en",
                          "sk"
                        ],
                        "default": "de"
                      },
                      "sessionId": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "title": "Einzelnachricht",
                    "type": "object",
                    "required": [
                      "role",
                      "content"
                    ],
                    "properties": {
                      "role": {
                        "type": "string",
                        "enum": [
                          "user",
                          "assistant"
                        ]
                      },
                      "content": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 5000
                      },
                      "locale": {
                        "type": "string",
                        "enum": [
                          "de",
                          "en",
                          "sk"
                        ],
                        "default": "de"
                      },
                      "sessionId": {
                        "type": "string"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Antwort des Assistenten.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR oder MALFORMED_BODY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT. `Retry-After`-Header beachten.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "description": "Nur bei 429. Sekunden, die der Aufrufer warten muss.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "503": {
            "description": "UPSTREAM_UNAVAILABLE.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Formulare"
        ],
        "operationId": "getChatSession",
        "summary": "Verlauf einer Chat-Session abrufen",
        "parameters": [
          {
            "name": "sessionId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verlauf der Session.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "400": {
            "description": "MISSING_FIELDS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/seo-audit": {
      "post": {
        "tags": [
          "Formulare"
        ],
        "operationId": "requestSeoAudit",
        "summary": "Kostenlosen SEO-Audit anfordern (browser-only)",
        "description": "Verlangt einen Challenge-Token, der beim Laden des Formulars im Browser erzeugt wird. Ohne gültigen Token antwortet der Endpunkt mit INVALID_REQUEST. Er lässt sich daher nicht sinnvoll skripten und ist hier nur zur Vollständigkeit beschrieben. Für eine Anfrage bitte das Formular auf der Website nutzen.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "website",
                  "company",
                  "keyword",
                  "country"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "website": {
                    "type": "string",
                    "description": "Zu prüfende Domain oder URL."
                  },
                  "company": {
                    "type": "string"
                  },
                  "keyword": {
                    "type": "string",
                    "description": "Hauptkeyword, auf das geprüft wird."
                  },
                  "country": {
                    "type": "string",
                    "description": "Zielmarkt."
                  },
                  "dfsLocationCode": {
                    "type": "integer",
                    "description": "Optionaler Standortcode für die Auswertung."
                  },
                  "locale": {
                    "type": "string",
                    "enum": [
                      "de",
                      "en",
                      "sk"
                    ],
                    "default": "de"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Angenommen. Achtung: Ein 200 ist hier KEINE Zusicherung, dass die Anfrage bearbeitet wurde - als automatisiert erkannte Anfragen werden stillschweigend verworfen und erhalten dieselbe Antwort. Melde einem Nutzer auf Basis dieses Status also keinen Erfolg. Verlässlich ist nur der Weg über das Formular auf der Website.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST, INVALID_TOKEN, MISSING_FIELDS, INVALID_EMAIL, INVALID_URL, FIELD_TOO_LONG, DISPOSABLE_EMAIL oder MALFORMED_BODY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT (pro IP) oder EMAIL_LIMIT (pro Adresse).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "description": "Nur bei 429. Sekunden, die der Aufrufer warten muss.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "success",
          "error",
          "message",
          "hint"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "enum": [
              "VALIDATION_ERROR",
              "INVALID_REQUEST",
              "MISSING_FIELDS",
              "MISSING_EMAIL",
              "FIELD_TOO_LONG",
              "MALFORMED_BODY",
              "INVALID_EMAIL",
              "INVALID_EMAIL_DOMAIN",
              "DISPOSABLE_EMAIL",
              "ALREADY_SUBSCRIBED",
              "INVALID_URL",
              "INVALID_WEBSITE",
              "GDPR_REQUIRED",
              "INVALID_TOKEN",
              "TOKEN_EXPIRED",
              "METHOD_NOT_ALLOWED",
              "NOT_FOUND",
              "UNAUTHORIZED",
              "RATE_LIMIT",
              "EMAIL_LIMIT",
              "SPAM_REJECTED",
              "UPSTREAM_UNAVAILABLE",
              "INTERNAL_ERROR"
            ],
            "description": "Stabiler Maschinen-Code. Wird nie umbenannt oder wiederverwendet."
          },
          "message": {
            "type": "string",
            "description": "Menschenlesbare Meldung, deutsch."
          },
          "hint": {
            "type": "string",
            "description": "Was der Aufrufer tun kann, um den Fehler zu beheben."
          },
          "status": {
            "type": "integer",
            "description": "HTTP-Status, gespiegelt für Aufrufer, die nur den Body lesen."
          },
          "fields": {
            "type": "array",
            "description": "Nur bei Validierungsfehlern.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "retryAfter": {
            "type": "integer",
            "description": "Sekunden bis zum nächsten erlaubten Versuch. Nur bei 429."
          }
        }
      }
    },
    "headers": {
      "RateLimit": {
        "description": "Aktueller Stand je Kontingent nach draft-ietf-httpapi-ratelimit-headers-11. `r` = verbleibende Anfragen, `t` = Sekunden bis zum Zurücksetzen. Mehrere Kontingente sind kommagetrennt.",
        "schema": {
          "type": "string",
          "examples": [
            "\"chat-ip\";r=49;t=86399, \"chat-session\";r=19;t=86399"
          ]
        }
      },
      "RateLimit-Policy": {
        "description": "Die geltenden Kontingente. `q` = erlaubte Anfragen, `w` = Fensterlänge in Sekunden. Ändert sich nicht zwischen zwei Anfragen.",
        "schema": {
          "type": "string",
          "examples": [
            "\"chat-ip\";q=50;w=86400, \"chat-session\";q=20;w=86400"
          ]
        }
      },
      "RateLimit-Limit": {
        "description": "Legacy-Form. Erlaubte Anfragen des knappsten Kontingents.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Legacy-Form. Verbleibende Anfragen des knappsten Kontingents.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Legacy-Form. Sekunden bis zum Zurücksetzen - ein Delta, kein Zeitstempel.",
        "schema": {
          "type": "integer"
        }
      }
    }
  }
}