{
  "openapi": "3.1.0",
  "info": {
    "title": "SchlüsselBot API",
    "version": "1.0.0",
    "summary": "Vertrauliche Nachrichten senden, ihren Versandstatus prüfen, zurückziehen und verschlüsselt gespeicherte Kontakte verwalten.",
    "description": "Business-API von SchlüsselBot, erstellt und veröffentlicht von MessingerDesign. Vollständige Anleitung: https://schluesselbot.de/api-docs. Rechtehinweis: https://schluesselbot.de/ai-policy.txt",
    "contact": {"name": "MessingerDesign", "url": "https://schluesselbot.de/impressum"},
    "license": {"name": "Proprietär – Nutzung nur mit gültigem SchlüsselBot-Business-Konto", "identifier": "LicenseRef-SchluesselBot-API"}
  },
  "servers": [{"url": "https://schluesselbot.de"}],
  "security": [{"bearerAuth": []}],
  "paths": {
    "/v1/messages": {
      "post": {
        "operationId": "createMessage",
        "summary": "Verschlüsselte Nachricht erstellen und optional zustellen",
        "description": "Benötigt messages:send. Idempotency-Key wird empfohlen. /v1/send bleibt als Alias erhalten.",
        "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}],
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateMessage"}}}},
        "responses": {
          "200": {"description": "Nachricht angelegt", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageCreated"}}}},
          "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/Conflict"},
          "413": {"$ref": "#/components/responses/TooLarge"}, "429": {"$ref": "#/components/responses/RateLimited"},
          "502": {"$ref": "#/components/responses/Unavailable"}, "503": {"$ref": "#/components/responses/Unavailable"}
        }
      }
    },
    "/v1/messages/{messageId}": {
      "parameters": [{"$ref": "#/components/parameters/MessageId"}],
      "get": {
        "operationId": "getMessageStatus", "summary": "Versandstatus abfragen", "description": "Benötigt messages:read.",
        "responses": {"200": {"description": "Aktueller Zustand", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageStatus"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "503": {"$ref": "#/components/responses/Unavailable"}}
      },
      "delete": {
        "operationId": "withdrawMessage", "summary": "Noch offene Nachricht zurückziehen", "description": "Benötigt messages:withdraw. Wiederholtes Zurückziehen ist sicher.",
        "responses": {"200": {"description": "Sendung dauerhaft gesperrt", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WithdrawResult"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "503": {"$ref": "#/components/responses/Unavailable"}}
      }
    },
    "/v1/contacts": {
      "get": {
        "operationId": "listContacts", "summary": "Kontakte durchsuchen und auflisten", "description": "Benötigt contacts:read.",
        "parameters": [
          {"name": "q", "in": "query", "schema": {"type": "string", "maxLength": 120}},
          {"name": "cursor", "in": "query", "schema": {"type": "integer", "minimum": 0, "default": 0}},
          {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}}
        ],
        "responses": {"200": {"description": "Kontaktseite", "content": {"application/json": {"schema": {"type": "object", "required": ["data","total"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Contact"}}, "next_cursor": {"type": ["integer","null"]}, "total": {"type": "integer"}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}}
      },
      "post": {
        "operationId": "createContact", "summary": "Kontakt anlegen", "description": "Benötigt contacts:write. Kontakte werden auf dem Server AES-256-GCM-verschlüsselt gespeichert.",
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactInput"}}}},
        "responses": {"201": {"description": "Kontakt angelegt", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Contact"}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/Conflict"}}
      }
    },
    "/v1/contacts/{contactId}": {
      "parameters": [{"name": "contactId", "in": "path", "required": true, "schema": {"type": "integer", "minimum": 1}}],
      "get": {"operationId": "getContact", "summary": "Kontakt laden", "description": "Benötigt contacts:read.", "responses": {"200": {"description": "Kontakt", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Contact"}}}}, "404": {"$ref": "#/components/responses/NotFound"}}},
      "patch": {"operationId": "updateContact", "summary": "Kontakt teilweise ändern", "description": "Benötigt contacts:write. name und email können einzeln übermittelt werden.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactPatch"}}}}, "responses": {"200": {"description": "Kontakt aktualisiert", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Contact"}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"$ref": "#/components/responses/Conflict"}}},
      "delete": {"operationId": "deleteContact", "summary": "Kontakt löschen", "description": "Benötigt contacts:write.", "responses": {"200": {"description": "Kontakt gelöscht", "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "integer"}, "deleted": {"const": true}}}}}}, "404": {"$ref": "#/components/responses/NotFound"}}}
    }
  },
  "components": {
    "securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "sb_live_…", "description": "Im Konto erzeugter API-Schlüssel mit den benötigten granularen Berechtigungen."}},
    "parameters": {
      "MessageId": {"name": "messageId", "in": "path", "required": true, "schema": {"type": "string", "pattern": "^[A-Fa-f0-9]{32,128}$"}},
      "IdempotencyKey": {"name": "Idempotency-Key", "in": "header", "required": false, "description": "Eindeutige Kennung je fachlichem Versand. Dieselbe Anfrage liefert 24 Stunden dieselbe Antwort; anderer Inhalt mit derselben Kennung ergibt 409.", "schema": {"type": "string", "maxLength": 128, "pattern": "^[A-Za-z0-9._:-]+$"}}
    },
    "schemas": {
      "CreateMessage": {
        "type": "object", "required": ["recipient_email"],
        "properties": {
          "recipient_email": {"type": "string", "format": "email"},
          "message": {"type": "string", "maxLength": 262144, "description": "Komfortweg: erreicht SchlüsselBot kurz im Klartext und wird im Arbeitsspeicher verschlüsselt."},
          "ciphertext": {"type": "string", "description": "Base64 aus 12-Byte-Nonce, AES-256-GCM-Chiffrat und 16-Byte-Tag."},
          "fragment": {"type": "string", "pattern": "^[A-Za-z0-9_-]{43}$", "description": "32-Byte-Schlüssel, base64url ohne Padding. Für automatische Zustellung erforderlich."},
          "attachment_ciphertext": {"type": "string", "description": "Bereits clientseitig verschlüsselter und base64-kodierter Dateicontainer; bis 100 MB Rohdaten im Business-Tarif."},
          "delivery_mode": {"type": "string", "enum": ["automatic","separate"], "default": "automatic"},
          "ttl_hours": {"type": "integer", "minimum": 1, "maximum": 168, "default": 48},
          "sender_name": {"type": "string", "maxLength": 80},
          "personal_note": {"type": "string", "maxLength": 500},
          "notify_email": {"type": "string", "format": "email"}
        },
        "oneOf": [{"required": ["message"]}, {"required": ["ciphertext"]}]
      },
      "MessageCreated": {"type": "object", "required": ["id","link","expires_at","modus","delivery","has_attachment"], "properties": {"id": {"type": "string"}, "link": {"type": "string", "format": "uri"}, "expires_at": {"type": "string", "format": "date-time"}, "modus": {"type": "string"}, "delivery": {"type": "string"}, "mail_sent": {"type": ["boolean","null"]}, "has_attachment": {"type": "boolean"}, "hinweis": {"type": "string"}}},
      "MessageStatus": {"type": "object", "required": ["id","status","created_at","expires_at"], "properties": {"id": {"type": "string"}, "status": {"type": "string", "enum": ["ready","consumed","withdrawn","expired","unknown"]}, "open": {"type": ["boolean","null"]}, "recipient": {"type": "string", "format": "email"}, "created_at": {"type": "string", "format": "date-time"}, "expires_at": {"type": "string", "format": "date-time"}, "consumed_at": {"type": ["string","null"], "format": "date-time"}, "withdrawn_at": {"type": ["string","null"], "format": "date-time"}, "mail_sent": {"type": ["boolean","null"]}, "has_attachment": {"type": "boolean"}}},
      "WithdrawResult": {"type": "object", "required": ["id","status"], "properties": {"id": {"type": "string"}, "status": {"const": "withdrawn"}, "already_withdrawn": {"type": "boolean"}, "removed_from_vault": {"type": "boolean"}, "hinweis": {"type": "string"}}},
      "Contact": {"type": "object", "required": ["id","name","email","created_at"], "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "email": {"type": "string", "format": "email"}, "created_at": {"type": "string", "format": "date-time"}}},
      "ContactInput": {"type": "object", "required": ["email"], "properties": {"name": {"type": "string", "maxLength": 80}, "email": {"type": "string", "format": "email"}}},
      "ContactPatch": {"type": "object", "minProperties": 1, "properties": {"name": {"type": "string", "maxLength": 80}, "email": {"type": "string", "format": "email"}}},
      "Error": {"type": "object", "required": ["error"], "properties": {"error": {"type": "string"}, "code": {"type": "string"}}}
    },
    "responses": {
      "BadRequest": {"description": "Ungültige Angaben", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "Unauthorized": {"description": "API-Schlüssel fehlt, ist ungültig oder widerrufen", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "Forbidden": {"description": "Tarif, Berechtigung oder Browser-Origin nicht zulässig", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "NotFound": {"description": "Nicht gefunden oder gehört nicht zum API-Konto", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "Conflict": {"description": "Dubletten- oder Idempotenzkonflikt", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "TooLarge": {"description": "Anfrage oder verschlüsselter Anhang ist zu groß", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "RateLimited": {"description": "Versandgrenze erreicht", "headers": {"Retry-After": {"schema": {"type": "integer"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "Unavailable": {"description": "Vorübergehender Betriebsfehler; mit gleichem Idempotency-Key erneut versuchen", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
    }
  }
}
