{
  "openapi": "3.0.3",
  "info": {
    "title": "ProofSnap API",
    "version": "2.0.0",
    "description": "Die ProofSnap API lädt eine Webseite in einem serverseitigen Chromium-Browser und liefert ein Beweispaket (ZIP).\n\nJedes Aufnahmepaket enthält den Screenshot, das HTML der Seite, den extrahierten Seitentext, metadata.json, forensic_log.json, chain_of_custody.json, evidence.pdf und eine manifest.json, die den SHA-256 jeder Datei auflistet. manifest.json wird mit einem für diese Aufnahme erzeugten RSA-4096-Schlüssel signiert (der öffentliche Schlüssel liegt als publickey.pem bei, der private Schlüssel wird verworfen), und der SHA-256 von manifest.json wird für eine Bitcoin-Verankerung an OpenTimestamps-Kalender übermittelt (manifest.json.ots). Schlägt die Kalenderanfrage fehl, wird das Paket ohne diesen Nachweis ausgeliefert und manifest.json listet ihn nicht auf, sodass die Offline-Skripte und das Prüfwerkzeug ihn als nicht vorhanden statt als fehlend melden. Mit `include_eidas` und einem Live-API-Schlüssel erhält derselbe Hash zusätzlich einen eIDAS-qualifizierten Zeitstempel (RFC 3161, manifest.json.tsr). JPEG-Screenshots werden soweit möglich mit C2PA Content Credentials signiert: Schlägt die Signatur fehl, wird die Aufnahme ohne sie abgeschlossen. Der Ordner verification/ enthält Skripte, die das Paket offline prüfen. Jede manifest.json enthält `ots_listed_only_when_present: true` (manifest.json.ots wird nur aufgeführt, wenn die Datei im Paket liegt) und `eidas_marker_version: 1`. Sie vermerkt `eidas_requested: true` nur, wenn das Paket mit seinem qualifizierten Zeitstempel ausgeliefert wird. Kann das Token nicht bezogen werden, wird das Manifest ohne es neu versiegelt. Ein Paket, das `eidas_requested: true` vermerkt, aber keine manifest.json.tsr enthält, hat sein Token also verloren, und die Offline-Skripte melden FAIL. chain_of_custody.json vermerkt `bitcoin_ots_included` und listet in `page_modifications.items[]` jede Änderung auf, die vor der Aufnahme an der Seite vorgenommen wurde. manifest.json enthält sealed_at (wann sie erstellt wurde, nach jedem Datei-Hash) und die per SNTP gemessenen Uhrabweichungen des Aufnahmeservers; chain_of_custody.json listet die Schritte der Aufnahme in der Reihenfolge ihres Ablaufs auf, jeweils mit dem Zeitpunkt ihres Abschlusses, und endet mit custody_record_written (Hashing, Signatur und Zeitstempel folgen danach und werden durch manifest.json und die Nachweise belegt).\n\nmetadata.json vermerkt:\n\n- `browser_identity`: den tatsächlich gesendeten User-Agent und die Client Hints.\n- `dns`: eine unabhängige DNS-Prüfung der Adresse, mit der sich der Browser verbunden hat, mit den Antworten von dns.google und cloudflare-dns.com und dem Ergebnis: confirmed, not_confirmed, resolvers_unavailable, connected_ip_unknown, skipped_ip_literal (die URL nennt eine IP-Adresse, es gibt also keinen Hostnamen zum Nachschlagen) oder skipped_internal_host (ein interner Hostname, der nie an die öffentlichen Resolver gesendet wird).\n- `url_at_load` und `url_at_screenshot`: die Adresse direkt nach dem Laden der Seite und zum Zeitpunkt der Bildaufnahme (eine Seite kann ihre eigene Adresse ändern, meist den Teil nach #).\n- `redirect_chain`: die Weiterleitungen des Hauptdokuments, älteste zuerst, jeweils mit HTTP-Status. Die angeforderte und die endgültige Adresse werden vollständig angegeben; bei jedem Zwischenschritt sind die Query-Werte durch [redacted] ersetzt (`query_values_redacted`).\n- `tls.source`: woher jedes TLS-Feld stammt, entweder `browser_connection` oder `browser_connection_and_separate_handshake`, Letzteres mit `at`, `ip` und `port` des separaten Handshakes, der certificate_chain.pem nach dem Laden der Seite abgerufen hat.\n\nWurde ein angeforderter Schritt nicht ausgeführt (ein eIDAS-Zeitstempel, die C2PA-Signatur, die Archivanfrage, WHOIS, der zweite Uhrzeitabgleich, ein durch das Zeitlimit abgeschnittenes WARC oder eine Wartezeit oder das Aufklappen eingeklappter Inhalte, das wegen des Zeitlimits übersprungen wurde), führt evidence.pdf ihn mit einer kurzen Begründung auf, und das Herkunftszertifikat nennt ihn in einer Zeile.\n\nAufnahmen laufen asynchron. POST /captures liefert 202 mit einer capture_id; fragen Sie GET /captures/{capture_id} ab oder übergeben Sie `options.webhook_url`. Eine abgeschlossene Aufnahme liefert eine vorsignierte package_url, die bis zu 24 Stunden gültig ist (siehe package_url_expires_at); rufen Sie die Aufnahme erneut ab, um einen neuen Link zu erhalten.\n\nDie folgenden Pfade sind relativ zur Server-URL, die bereits die Stage /v1 enthält. Von der API gelieferte `poll_path`-Werte enthalten /v1.",
    "contact": {
      "email": "support@getproofsnap.com"
    }
  },
  "servers": [
    {
      "url": "https://api.getproofsnap.com/v1",
      "description": "ProofSnap API (EU)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Aufnahmen",
      "description": "Aufnahmen erstellen, auflisten, lesen, löschen und prüfen.\n\n**Rate-Limits.** `POST /captures`, `POST /captures/batch`, `POST /compare`, `POST /captures/{capture_id}/redact` und `POST /certify-hash` unterliegen einem Rate-Limit pro Konto in festen 60-Sekunden-Zeitfenstern: Alle API-Schlüssel des Kontos und seine Dashboard-Sitzung teilen sich ein Zeitfenster (zusätzliche Schlüssel erhöhen das Limit nicht). Ein Batch oder eine Zertifizierung mehrerer Hashes verbraucht eine Einheit pro URL bzw. Hash. Standardlimits pro Zeitfenster nach Tarif:\n\n| Tarif | Anfragen pro 60 s |\n| --- | --- |\n| sandbox | 10 |\n| developer | 60 |\n| startup | 120 |\n| growth | 300 |\n| custom | 1.200 |\n| legal_solo | 60 |\n| legal_seats | 120 |\n| website_archive | 60 |\n| archive_only | 30 |\n| jeder andere Tarif | 30 |\n\nDie folgenden Anfragen verbrauchen ebenfalls Einheiten aus demselben Zeitfenster, eine Einheit pro Anfrage, sofern nicht anders angegeben: `POST /captures/{capture_id}/verify`, `PATCH /captures/{capture_id}` mit der Aktion legal_hold, `GET /captures/hosts` (nur wenn die Liste neu berechnet wird, nicht wenn ein zwischengespeichertes Ergebnis geliefert wird), `POST /legal-holds` (eine Einheit pro 10 Aufnahmen), `POST /captures/export-jobs` sowie `POST /imports`, `POST /imports/{import_id}/complete` und `GET /imports/{import_id}?upload=true`.\n\nWird das Limit überschritten, liefert die API 429 RATE_LIMITED mit den Headern `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` und `X-RateLimit-Reset` sowie `error.details.retry_after_seconds`. Eine Anfrage, die mehr Einheiten benötigt, als ein Zeitfenster zulässt (eine Zertifizierung mehrerer Hashes, die das Limit pro Zeitfenster übersteigt), kann nie erfolgreich sein und liefert 400 RATE_LIMIT_COST_TOO_HIGH mit `error.details` `{ max_per_window, window_seconds, requested }`. Teilen Sie sie in kleinere Anfragen auf. Eine abgelehnte Anfrage verbraucht keine Einheiten, und die Header melden das tatsächlich verbleibende Budget.\n\n**Monatliche Limits.** Aufnahmen, Vergleiche, Schwärzungen und Zertifizierungen mit Live-API-Schlüsseln werden auf ein monatliches Maximum pro Konto angerechnet (UTC-Kalendermonat). Die angenommene Antwort enthält `X-Quota-*`-Header. Ist das Maximum erreicht, wird 402 QUOTA_EXCEEDED mit `error.details` `{ reason, used, limit, max_limit, cap, org_member }` geliefert, wobei `cap` den Wert `plan_max`, `customer`, `member_owner` oder `org` hat. Solange ein Live-Abonnement überfällig oder unbezahlt ist, liefern kostenpflichtige Anfragen 402 PAYMENT_REQUIRED; für ein solches Konto werden Aufnahmen (einzeln und im Batch) unabhängig von der Schlüsselumgebung mit diesem Fehler abgelehnt, auch mit Testschlüsseln.\n\n**Laufende Aufnahmen und Testmodus.** Jedes Konto kann nur eine begrenzte Zahl von Aufnahmen gleichzeitig ausführen (developer 10, startup 25, growth 100, legal_solo und legal_seats 25, custom 200, website_archive 25, sandbox 5). Oberhalb dieses Limits liefert die API 429 CONCURRENT_CAPTURE_LIMIT; ein Batch startet die URLs, die hineinpassen, und listet die übrigen in `not_admitted` auf (jeweils mit `code` CONCURRENT_CAPTURE_LIMIT sowie `in_flight_limit`), senden Sie diese also später erneut. Arbeit im Testmodus (ein Testschlüssel oder eine Sitzung ohne verknüpfte Abrechnung) ist auf 200 Einheiten pro Konto und UTC-Tag begrenzt (429 TEST_MODE_DAILY_LIMIT; `X-Test-Daily-*`-Header), und eine Aufnahme im Testmodus wird nie an das Internet Archive gesendet (`external_archive.skip_reason` \"test_mode\")."
    },
    {
      "name": "Vergleichen und Schwärzen",
      "description": "Ein neues versiegeltes Paket aus bestehenden Aufnahmen ableiten: einen Vergleich zweier Aufnahmen oder eine geschwärzte Kopie einer Aufnahme."
    },
    {
      "name": "Hash-Zertifizierung",
      "description": "SHA-256-Hashes von Dateien zertifizieren, die Sie selbst aufbewahren. Es werden nur die Hashes gesendet; die Dateien erreichen ProofSnap nie."
    },
    {
      "name": "Automatische Aufnahmen",
      "description": "Wiederkehrende Aufnahmen (Intervall oder Cron) und Änderungsüberwachung (on_change). Jeder Lauf, der eine Aufnahme erstellt, ist eine normale Aufnahme: Er wird abgerechnet und auf die monatlichen Limits angerechnet."
    },
    {
      "name": "Mandate",
      "description": "Arbeitsbereiche für Rechtsfälle mit einem hash-verketteten Beweiskettenprotokoll. Verfügbar in den Legal-Tarifen (legal_solo, legal_seats); andere Tarife erhalten 403 MATTERS_NOT_ENABLED, mit zwei Ausnahmen. Der Inhaber eines Unternehmens, dessen Abonnement beendet ist, kann die Mandate dieses Unternehmens bis zum Löschdatum der Unternehmensdaten (data_delete_after) weiterhin auflisten, öffnen und exportieren; dieser Zugriff ist schreibgeschützt zuzüglich Export, und GET /matters fügt dann read_only: true hinzu. Ein Konto im Tarif Archive only kann seine Mandate auflisten, öffnen und exportieren. Nur der Mandatsinhaber darf ein Mandat umbenennen, archivieren, Mitglieder verwalten, versiegeln, teilen oder exportieren. Mandatsinhaber ist sein Ersteller. Bei einem Unternehmensmandat wird der Unternehmensinhaber erst dann Mandatsinhaber, wenn der Ersteller das Unternehmen verlassen hat. Ein Unternehmensinhaber, der als Mitglied hinzugefügt wurde, hat nur Mitgliederrechte. Mitglieder erhalten für diese Vorgänge 403 FORBIDDEN. Jeder Schreibvorgang an einem Mandat (Erstellen, PATCH, Zuordnen von Aufnahmen, Teilen und dessen Widerruf, Versiegeln, Export) erfordert eine bestätigte E-Mail-Adresse (403 EMAIL_NOT_VERIFIED, für eine Dashboard-Sitzung und für einen API-Schlüssel eines Kontos, dessen Adresse nicht bestätigt ist) und wird für einen Live-Aufrufer (Live-API-Schlüssel oder eine Sitzung mit verknüpfter Abrechnung) mit 402 PAYMENT_REQUIRED abgelehnt, solange das Abonnement (oder das des Unternehmens) überfällig oder unbezahlt ist. Ein archiviertes Mandat lehnt Umbenennungen, neue Mitglieder, neue Aufnahmen, Teilen und Export ab (409 MATTER_ARCHIVED), bis es wieder geöffnet wird; ein Aufrufer, der es nicht wieder öffnen kann (der Inhaber eines beendeten Unternehmens, ein Archive-only-Konto), kann es dennoch exportieren."
    },
    {
      "name": "Webhooks",
      "description": "ProofSnap sendet per POST ein JSON-Ereignis an die `webhook_url`, die Sie mit einer Aufnahme, Zertifizierung, Schwärzung oder einem Zeitplan übergeben. Die URL muss https verwenden, darf keine Anmeldedaten enthalten (`user:password@`, 400 INVALID_URL) und darf nicht auf eine private, Loopback-, Link-Local-, Carrier-Grade-NAT-, Multicast-, reservierte oder Cloud-Metadaten-Adresse zeigen, einschließlich IPv6-Formen, die eine solche IPv4-Adresse einbetten. Das wird bei der Annahme der Anfrage geprüft (422 SSRF_BLOCKED) und erneut bei jeder Zustellung: Der Hostname wird aufgelöst, jede Adresse wird geprüft, und die Zustellung verbindet sich nur mit einer Adresse, die die Prüfung bestanden hat, sodass ein Host, der später auf eine gesperrte Adresse aufgelöst wird, nicht kontaktiert wird. Kann der Hostname nicht aufgelöst werden, wird die Zustellung nicht gesendet. Auf jeder Route wird eine leere oder nur aus Leerzeichen bestehende webhook_url so behandelt, als gäbe es keinen Webhook. /compare sendet keine Webhooks.\n\n**Konto-Abonnements.** Statt (oder zusätzlich zu) einer webhook_url pro Anfrage abonnieren Sie mit `POST /webhook-subscriptions` einen Endpunkt für Kontoereignisse: Jedes passende Ereignis des Kontos geht an ihn, unabhängig davon, welcher Schlüssel, Zeitplan oder welche Dashboard-Sitzung es ausgelöst hat. Jedes Abonnement hat ein eigenes Secret (wird einmalig bei der Erstellung geliefert), listet die gewünschten Ereignistypen auf und kann nach `host` oder `schedule_id` filtern; ein Live-Abonnement erhält nur Ereignisse für Live-Aufnahmen, ein Test-Abonnement nur Ereignisse für Testaufnahmen. Ein API-Schlüssel listet und ändert nur die Abonnements seiner eigenen Umgebung; die Dashboard-Sitzung sieht alle. Ihre Zustellungen enthalten `subscription_id` auf oberster Ebene und keinen vorsignierten Link (package_url, package_url_expires_at, upgraded_proof_url und compare.package_url werden entfernt; monitor.change_detected enthält compare.compare_id): Rufen Sie das Paket mit GET /captures/{capture_id} ab. Aufnahmeereignisse enthalten außerdem `capture_id` auf oberster Ebene. Die webhook-id einer Abonnement-Zustellung wird aus dem Abonnement, der Aufnahme (oder dem Zeitplan) und dem Ereignis abgeleitet. Es gelten die unten beschriebenen Zustellregeln und Wiederholungen. Zusätzlich deaktiviert eine 410-Antwort das Abonnement sofort (disabled_reason gone), Zustellungen, die 5 Tage lang ohne Erfolg fehlschlagen (und mindestens einen vollständigen Wiederholungszyklus fehlgeschlagener Versuche umfassen), deaktivieren es (failing), und der Widerruf des API-Schlüssels, der es erstellt hat, deaktiviert es (key_revoked); PATCH mit status \"active\" reaktiviert es. Jedes Ereignis wird außerdem 30 Tage lang in `GET /events` aufbewahrt.\n\n**Header**\n\nJede Zustellung wird gemäß der Spezifikation [Standard Webhooks](https://www.standardwebhooks.com/) signiert, sodass jede offizielle Standard-Webhooks-Bibliothek sie prüfen kann:\n\n| Header | Wert |\n| --- | --- |\n| `Content-Type` | `application/json` |\n| `webhook-id` | `msg_<delivery id>`: identisch für dasselbe Ereignis derselben Aufnahme, sodass eine doppelte Zustellung dieselbe ID trägt. Nutzen Sie sie zur Deduplizierung: Verarbeiten Sie jede webhook-id einmal. `GET /webhook-events/{capture_id}` liefert dieselbe ID ohne das Präfix `msg_` als `delivery_id` |\n| `webhook-timestamp` | Unix-Zeit in Sekunden, zu der die Zustellung gesendet wurde. Sie ist von der Signatur abgedeckt |\n| `webhook-signature` | `v1,<base64>`: HMAC-SHA256 über `<webhook-id>.<webhook-timestamp>.<raw body>`. Während einer Rotation des Webhook-Secrets enthält er zwei durch Leerzeichen getrennte Einträge, `v1,<new> v1,<previous>`; akzeptieren Sie die Zustellung, wenn ein beliebiger Eintrag übereinstimmt |\n| `User-Agent` | `ProofSnap-Webhook/2.0` |\n\n**Zustellung prüfen.** Prüfen Sie über die exakt empfangenen Bytes, vor dem Parsen. Lehnen Sie eine Zustellung ab, deren webhook-timestamp um mehr als 5 Minuten von Ihrer Uhr abweicht (die offiziellen Bibliotheken tun das), und deduplizieren Sie anhand der webhook-id. Der Signaturschlüssel ist der Teil des Secrets nach `whsec_`, base64-dekodiert. `webhook_secret` ist `whsec_` gefolgt von Standard-Base64, also die Form, die die offiziellen Bibliotheken erwarten; übergeben Sie es ihnen unverändert.\n\n```js\n// npm install standardwebhooks\nimport { Webhook } from \"standardwebhooks\";\nconst wh = new Webhook(process.env.PROOFSNAP_WEBHOOK_SECRET); // whsec_... (webhook_secret)\n// rawBody: the request body as received (string or Buffer), headers: the request headers\nconst event = wh.verify(rawBody, headers); // throws on a bad signature or a timestamp outside 5 minutes\n```\n\nOhne Bibliothek (Node):\n\n```js\nconst key = Buffer.from(secret.slice(\"whsec_\".length), \"base64\");\nconst signed = `${headers[\"webhook-id\"]}.${headers[\"webhook-timestamp\"]}.${rawBody}`;\nconst expected = crypto.createHmac(\"sha256\", key).update(signed).digest(\"base64\");\nconst ok = headers[\"webhook-signature\"].split(\" \").some((part) => {\n  const [version, sig] = part.split(\",\");\n  return version === \"v1\" && sig && sig.length === expected.length\n    && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n});\n// and: Math.abs(Date.now() / 1000 - Number(headers[\"webhook-timestamp\"])) <= 300\n```\n\n**Webhook-Secret.** Jeder API-Schlüssel hat sein eigenes Secret (`whsec_...`). Es wird einmalig angezeigt, wenn Sie den Schlüssel im Dashboard unter Entwickler erstellen (das Feld `webhook_secret` der Antwort, die den Schlüssel erstellt). Es kann später nicht erneut ausgelesen werden: Die Schlüsselliste liefert es nie. Wenn Sie es verlieren oder es möglicherweise offengelegt wurde, erzeugen Sie im Dashboard unter Entwickler ein neues (Neues Webhook-Secret am Schlüssel). Auch das neue Secret wird nur einmal angezeigt. **Überlappung bei der Rotation:** 24 Stunden lang nach einer Rotation (bis `previous_secret_expires_at` in der Rotationsantwort) wird jede Zustellung sowohl mit dem neuen als auch mit dem vorherigen Secret signiert (zwei `v1,`-Einträge in webhook-signature), sodass Sie Ihren Endpunkt auf das neue Secret umstellen können, ohne Zustellungen zu verlieren. Eine zweite Rotation, solange diese Überlappung noch läuft, wird mit 409 WEBHOOK_SECRET_ROTATION_PENDING und `error.details.previous_secret_expires_at` (wann Sie wieder rotieren können) abgelehnt, sodass nie mehr als zwei Secrets gleichzeitig aktiv sind und ein Secret, auf das sich Empfänger noch verlassen könnten, nie vorzeitig außer Betrieb genommen wird. Bei einem offengelegten Secret widerrufen Sie den Schlüssel und erstellen einen neuen. Der Widerruf des Schlüssels löscht beide. Zustellungen verwenden die Secrets des Schlüssels zum Zeitpunkt des Versands, auch Zustellungen für Arbeit, die vor einer Rotation begonnen wurde. Eine Anfrage aus einer angemeldeten Dashboard-Sitzung hat keinen API-Schlüssel und damit kein Webhook-Secret, daher wird eine `webhook_url` darin mit 400 `WEBHOOK_REQUIRES_API_KEY` abgelehnt (Aufnahmen, Batches, Schwärzungen, Hash-Zertifizierungen, Erstellen oder Bearbeiten einer automatischen Aufnahme). Geplante Läufe verwenden das Secret des Schlüssels, der den Zeitplan erstellt hat. Wurde der Schlüssel widerrufen oder kann sein Secret zum Zeitpunkt der Zustellung nicht gelesen werden, wird das Ereignis nicht gesendet und der Versuch mit dem Grund protokolliert (lesbar mit `GET /webhook-events/{capture_id}`).\n\n**Zustellung.** Jeder Versuch hat ein Timeout von 10 Sekunden. Weiterleitungen werden nie verfolgt: Eine 3xx-Antwort zählt als Fehlschlag, ebenso wie eine Antwort außerhalb von 2xx oder ein Timeout. Der Antworttext wird nicht gelesen. **Wiederholungen:** Eine Zustellung, die in ein Timeout läuft, keine Verbindung herstellen kann oder eine Antwort außerhalb von 2xx erhält (außer 410 Gone), wird etwa 5 Minuten, 30 Minuten, 2 Stunden, 8 Stunden und 24 Stunden später erneut gesendet (Versuche 2 bis 6, insgesamt etwa 35 Stunden), jedes Mal mit derselben `webhook-id` und einem neuen `webhook-timestamp`. Danach wird die Zustellung aufgegeben. Eine wiederholte Zustellung enthält dieselbe Payload OHNE den 24-Stunden-Download-Link (`package_url`, `package_url_expires_at`): Rufen Sie das Paket mit GET /captures/{capture_id} ab. Zustellungen für eine zwischenzeitlich gelöschte Aufnahme werden verworfen. Ein vorübergehender Fehler beim Lesen des Signatur-Secrets auf unserer Seite wird auf dieselbe Weise wiederholt. Ein 410 Gone, eine Weiterleitung, eine gesperrte Adresse oder ein fehlender bzw. widerrufener Schlüssel wird nicht wiederholt. Der Text einer Wiederholung kann vom ersten Versuch abweichen (kein Download-Link): Deduplizieren Sie anhand der webhook-id, nicht anhand eines Hashs des Texts. Antworten Sie schnell mit 2xx. Das Versuchsprotokoll vermerkt eine allgemeine Fehlerklasse (connection_failed, timeout, blocked_address, redirect_not_followed, http_<status>), nie den rohen Netzwerkfehler. certify.completed und redaction.completed werden asynchron gesendet, kurz nachdem die Antwort geliefert wurde. Jeder Versuch wird protokolliert; lesen Sie das Protokoll mit `GET /webhook-events/{capture_id}`.\n\n**Ereignisse**\n\n`capture.completed`: Eine Aufnahme (einzeln, Batch-Element oder Lauf eines Intervall- bzw. Cron-Zeitplans) wurde abgeschlossen. Konto-Webhook-Abonnements erhalten es auch für die Ausgangs- und Änderungsaufnahmen eines on_change-Zeitplans (die eigene webhook_url des Zeitplans erhält dafür monitor.change_detected). `capture` hat dieselben Felder wie ein abgeschlossenes `GET /captures/{capture_id}`, zuzüglich `timestamps.eidas`, wenn ein qualifizierter Zeitstempel hinzugefügt wurde, `schedule_id` (null außerhalb eines Zeitplans), `test_mode` und `completed_at`.\n\n```json\n{ \"event\": \"capture.completed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"completed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"captured_at\": \"...\", \"completed_at\": \"...\",\n    \"package_url\": \"https://...\", \"package_url_expires_at\": \"...\", \"verification_url\": \"https://...\",\n    \"manifest_hash\": \"sha256:...\", \"timestamps\": { \"bitcoin_ots\": { \"status\": \"pending_confirmation\", \"block_height\": null } },\n    \"c2pa\": { \"included\": true, \"signed_screenshot_sha256\": \"sha256:...\" }, \"file_count\": 21, \"size_bytes\": 1234567, \"metadata\": {} } }\n```\n\n`capture.failed`: Die Aufnahme konnte nicht abgeschlossen werden. `error.message` ist eine kurze öffentliche Begründung (bei einem unerwarteten Fehler \"The capture could not be completed.\"); interne Fehlerdetails werden nie gesendet. Das umfasst auch einen Lauf, der abgestürzt oder in ein Timeout gelaufen ist: Er wird von einem Bereinigungslauf etwa 30 Minuten nach seinem Start als fehlgeschlagen markiert, und capture.failed wird dann gesendet (Meldung \"Capture did not finish (timed out).\"). Derselbe Bereinigungslauf sendet certify.failed oder redaction.failed für eine Hash-Zertifizierung oder Schwärzung, die nicht abgeschlossen wurde. Ein Lauf, der abgeschlossen wird, nachdem er als fehlgeschlagen gemeldet wurde, wird nicht berechnet und sendet kein capture.completed.\n\n```json\n{ \"event\": \"capture.failed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"failed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CAPTURE_FAILED\", \"message\": \"...\" }, \"metadata\": {} } }\n```\n\n`certify.completed`: Eine Hash-Zertifizierung wurde abgeschlossen. `certificate` hat dieselben Felder wie die synchrone Antwort von `POST /certify-hash`. Wird asynchron zugestellt, kurz nachdem die Antwort geliefert wurde.\n\n`certify.failed`: Eine Hash-Zertifizierung konnte nicht abgeschlossen werden.\n\n```json\n{ \"event\": \"certify.failed\",\n  \"certificate\": { \"certify_id\": \"cert_...\", \"status\": \"failed\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CERTIFY_FAILED\", \"message\": \"The certification could not be completed. Retry the request.\" } } }\n```\n\n`redaction.completed`: Eine Schwärzung wurde abgeschlossen. `redaction` hat dieselben Felder wie die Antwort von `POST /captures/{capture_id}/redact`.\n\n`redaction.failed`: Eine Schwärzung konnte nicht abgeschlossen werden. `error.code` ist der Code, den die Anfrage geliefert hätte (zum Beispiel REDACTION_LEAK_DETECTED), mit `details`, sofern vorhanden; eine Schwärzung, die der Bereinigungslauf als fehlgeschlagen markiert hat, trägt REDACTION_FAILED.\n\n```json\n{ \"event\": \"redaction.failed\",\n  \"redaction\": { \"redaction_id\": \"red_...\", \"status\": \"failed\", \"redacted_from\": \"cap_...\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"REDACTION_LEAK_DETECTED\", \"message\": \"...\", \"details\": { \"files\": [\"page.html\"], \"rule_indexes\": [0], \"metadata_fields\": [], \"in_capture_url\": false } } } }\n```\n\n`monitor.change_detected`: Ein `on_change`-Zeitplan hat eine Änderung des Seitentexts festgestellt und eine neue Aufnahme erstellt.\n\n```json\n{ \"event\": \"monitor.change_detected\", \"schedule_id\": \"sch_...\",\n  \"before_capture_id\": \"cap_...\", \"after_capture_id\": \"cap_...\", \"url\": \"https://example.com\",\n  \"compare\": { \"package_url\": \"https://...\", \"diff_summary\": { } },\n  \"diff_status\": \"ok\", \"verdict\": \"changed\", \"text_verdict\": \"changed\", \"images_verdict\": \"unchanged\", \"metadata_verdict\": \"unchanged\",\n  \"detected_at\": \"...\", \"test_mode\": false }\n```\n\n`verdict`, `text_verdict`, `images_verdict` und `metadata_verdict` (seit 2026-10-14) spiegeln die Ergebnisse des Vergleichs je Kategorie in compare.diff_summary wider (null ohne Vergleich). Ein Zeitplan mit `alert_on: \"text\"` sendet monitor.change_detected nur, wenn text_verdict den Wert changed hat (oder wenn kein Vergleichsergebnis vorliegt).\n\n`capture.anchor_confirmed` (nur Abonnements und GET /events): Die Bitcoin-Verankerung des OpenTimestamps-Nachweises des Pakets wurde bestätigt, meist einige Stunden nach der Aufnahme.\n\n```json\n{ \"event\": \"capture.anchor_confirmed\", \"capture_id\": \"cap_...\", \"schedule_id\": null, \"url\": \"https://example.com\",\n  \"confirmed_at\": \"...\", \"bitcoin_block_height\": 968549, \"upgraded_proof_available\": true, \"test_mode\": false }\n```\n\n`schedule.paused` (nur Abonnements und GET /events): ProofSnap hat einen Zeitplan pausiert. `reason` ist `no_billing` (kein API-Zugang), `plan_downgrade` (der Tarif erlaubt weniger Zeitpläne, kein on_change oder keine neuen Vorgänge), `failures` (fünf fehlgeschlagene Läufe in Folge), `key_revoked` oder `invalid_cron`. Eine Pause, die Sie selbst vornehmen, ist kein Ereignis.\n\n```json\n{ \"event\": \"schedule.paused\", \"schedule_id\": \"sch_...\", \"url\": \"https://example.com\", \"reason\": \"failures\",\n  \"paused_at\": \"...\", \"test_mode\": false }\n```\n\n`diff_status` ist `ok` (compare enthält den Vergleich), `no_baseline` (keine frühere Aufnahme zum Vergleichen) oder `failed` (der Vergleich ist fehlgeschlagen; compare ist null)."
    },
    {
      "name": "Öffentlich",
      "description": "Vorgänge, die keinen API-Schlüssel benötigen."
    },
    {
      "name": "Paketimport",
      "description": "Bestehende ProofSnap-Beweispakete (ZIP-Dateien, die mit der ProofSnap-Erweiterung erstellt wurden: Web-Aufnahmen, Sitzungen mit mehreren Tabs, einzelne und kombinierte Dateizertifizierungen; oder mit dieser API: Aufnahmen, Zertifizierungen, Vergleiche, Schwärzungen) in Ihr Konto importieren, damit sie wie Ihre API-Aufnahmen behandelt werden: 10 Jahre lang aufbewahrt und verfügbar für Löschsperre, Mandate und Exporte.\n\nNur in den Legal-Tarifen (legal_solo, legal_seats), mit einem Live-API-Schlüssel oder einer Dashboard-Sitzung mit verknüpfter Abrechnung (ein Testschlüssel, eine Sitzung ohne Abrechnung und die Sandbox erhalten 403 IMPORT_NOT_ALLOWED). Eine nicht bestätigte E-Mail-Adresse erhält 403 EMAIL_NOT_VERIFIED, ein überfälliges Abonnement 402 PAYMENT_REQUIRED. Ein Import ist keine Aufnahme: Er wird nicht auf die monatlichen Limits angerechnet und hat keine Gebühr pro Import; berechnet werden nur die gespeicherten Daten.\n\n**Ablauf.** 1. `POST /imports` mit dem Dateinamen und der exakten Dateigröße in Bytes (höchstens 2 GiB): Die Antwort enthält eine vorsignierte URL pro Teil von 32 MiB (der letzte Teil ist kleiner), gültig für 1 Stunde. 2. Senden Sie die Bytes jedes Teils per PUT an seine URL und bewahren Sie jeweils den Antwort-Header `ETag` auf. 3. `POST /imports/{import_id}/complete` mit jeder Teilnummer und ihrem ETag. 4. Fragen Sie `GET /imports/{import_id}` ab, bis der Status endgültig ist.\n\n**Status.** `uploading` (wartet auf die Uploads der Teile und den complete-Aufruf; ein Upload, der nicht innerhalb von 24 Stunden abgeschlossen wird, wird zu `expired`), `verifying` (das Paket wird geprüft), `accepted` (die Prüfung war erfolgreich: `capture_id` ist die neue Aufnahme, mit derselben ID wie der Import), `rejected` (die Prüfung ist fehlgeschlagen: `reasons` listet die Codes auf; außer dem Importdatensatz wird nichts aufbewahrt), `duplicate` (Ihr Konto enthält bereits eine Aufnahme mit demselben Manifest: `duplicate_of`), `expired`.\n\n**Prüfung.** Streng: die RSA-Signatur über manifest.json, jede aufgeführte Datei vorhanden mit dem aufgeführten SHA-256 (und SHA-512 und Größe, wo aufgeführt), keine Dateien, die das Manifest nicht aufführt (außer Dateien, die das Betriebssystem hinzufügt, etwa .DS_Store, und den aus dem Manifest abgeleiteten Nachweisen), mindestens eine gehashte Datei, eine konsistente Beweis-ID, die neu berechnete Hash-Kette des forensischen Protokolls, der OpenTimestamps-Nachweis gegen das Manifest (offline) und der eIDAS-qualifizierte Zeitstempel gegen das fest hinterlegte Disig-Stammzertifikat mit den Validierungsdaten im Paket. Die ZIP-Struktur wird geprüft, bevor etwas gelesen wird (keine Verschlüsselung, kein ZIP64, keine Links, sichere Namen, keine doppelten Namen, Größen und CRC-32 jedes Eintrags). Ein Testpaket der API (mit Wasserzeichen) wird abgelehnt. API-Pakete, die versiegelt wurden, bevor evidence.pdf gehasht wurde, werden angenommen, wobei diese Datei in `verification.checks.files.unhashed` aufgeführt wird.\n\n**Stufen** (`summary`): `integrity` (verified oder failed), `time_anchor` (eidas_qualified, eidas_qualified_revocation_unknown, ots_bitcoin_attested_unconfirmed, ots_pending, none) und `provenance`: für API-Pakete `issued_by_proofsnap` (das Manifest steht im Register der von der API ausgestellten Pakete) oder `unverified`; für Pakete der Erweiterung `registered_by_extension_account` (ein angemeldeter Nutzer der Erweiterung hat dieses Manifest bei seiner Erstellung registriert) oder `not_registered`; `unknown`, wenn die Abfrage nicht möglich war. Die Herkunft lässt einen Import nie fehlschlagen.\n\n**Die Aufnahme.** Ein angenommener Import ist eine Aufnahme mit `source` \"import\": Sie kann heruntergeladen werden (die hochgeladene ZIP Byte für Byte) und ist prüfbar (POST /captures/{capture_id}/verify führt dieselben Prüfungen erneut aus), kann unter Löschsperre gestellt, in den Papierkorb verschoben und gelöscht werden, erscheint in Mandaten (beim Import mit `matter_id` vermerkt das Beweiskettenprotokoll des Mandats `package.imported`) und in Exporten und wird 10 Jahre ab dem Import aufbewahrt. Ihr `captured_at` ist die Zeit, die das Paket selbst angibt (`import.captured_at_basis` \"package_claim\"), nie eine von ProofSnap beobachtete Zeit; `created_at` ist der Zeitpunkt, zu dem der Upload erstellt wurde. Vergleiche und Schwärzungen eines importierten Pakets werden abgelehnt (422 IMPORT_NOT_SUPPORTED).\n\n**Limits.** Höchstens 3 laufende Importe (uploading oder verifying) pro Konto (409 IMPORT_LIMIT), 50 erstellte Importe pro Konto und UTC-Tag (429 RATE_LIMITED, `details.action` \"import_create\"; jede Erstellungsanfrage zählt, auch eine, die später abgelehnt wird) sowie das Rate-Limit pro Minute des Tarifs bei create und complete. Importvolumen: höchstens 5 GiB pro UTC-Tag und 20 GiB pro UTC-Monat an angekündigten Paketgrößen pro Konto, gezählt bei create (und bei complete für einen älteren Upload); darüber 429 IMPORT_DAILY_BYTES_LIMIT oder IMPORT_MONTHLY_BYTES_LIMIT mit `Retry-After`; ein abgebrochener Upload zählt nicht."
    }
  ],
  "paths": {
    "/captures": {
      "post": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "createCapture",
        "summary": "URL aufnehmen",
        "description": "Nimmt die Aufnahme an und liefert sofort 202; die Aufnahme läuft im Hintergrund. Fragen Sie `GET /captures/{capture_id}` ab oder übergeben Sie `options.webhook_url`. Der Hostname der URL wird aufgelöst und geprüft, bevor die Aufnahme angenommen wird.\n\nRate-Limit pro Konto in 60-Sekunden-Zeitfenstern (alle API-Schlüssel des Kontos und seine Dashboard-Sitzung teilen sich ein Zeitfenster). Sobald der Limiter gelaufen ist, enthalten Antworten dieser Route die Header `RateLimit-Policy` und `RateLimit` (auf Basis von IETF draft-ietf-httpapi-ratelimit-headers) sowie die älteren `X-RateLimit-Limit`, `X-RateLimit-Remaining` und `X-RateLimit-Reset`. Eine 429 fügt `Retry-After` hinzu.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptureRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Angenommen. Eine idempotente Wiederholung einer früheren Anfrage mit demselben Idempotency-Key liefert ebenfalls 202, mit `Idempotent-Replay: true` und dem ursprünglichen Body.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureAccepted"
                },
                "example": {
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "status": "processing",
                  "url": "https://example.com",
                  "poll_path": "/v1/captures/cap_a1B2c3D4e5F6g7H8",
                  "message": "Capture accepted. Poll GET /v1/captures/{capture_id} (or use webhook_url) for completion.",
                  "metadata": {}
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST, MISSING_FIELD, INVALID_URL, INVALID_TIMESTAMP_TYPE, INVALID_VIEWPORT, METADATA_TOO_LARGE oder INVALID_EVIDENCE_PACK_OPTIONS. WEBHOOK_REQUIRES_API_KEY: webhook_url aus einer Dashboard-Sitzung gesendet (Webhooks benötigen einen API-Schlüssel).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED (monatliches Maximum oder Sandbox-Kontingent; enthält die Header X-Quota-* oder X-Sandbox-*) oder PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas mit einem Testschlüssel (ein ps_live_-Schlüssel eines Kontos ohne verknüpftes Abrechnungskonto läuft als Testschlüssel). EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist. PLAN_ARCHIVE_ONLY: Der Tarif bewahrt nur das Archiv auf und erstellt keine neuen Vorgänge. PLAN_API_READ_ONLY: ein API-Schlüssel eines Website-Archive-Kontos (sein Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Website-Archive-Kontos, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge werden in diesem Tarif nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED, TARGET_UNREACHABLE oder IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zu viele Anfragen im aktuellen 60-Sekunden-Zeitfenster. `error.details.retry_after_seconds` wiederholt Retry-After. RateLimit meldet r=0 und die Sekunden bis zum Zurücksetzen des Zeitfensters. Ebenfalls RATE_LIMITED bei einer Dashboard-Sitzung von Website Archive: deren Tagesobergrenze von 100 Einheiten pro UTC-Tag, gemeinsam genutzt von Einzel- und Batch-Aufnahmen (eine Einheit pro URL), compare, redact und certify-hash (eine Einheit pro Anfrage); eine Anfrage, die nicht hineinpasst, wird vollständig abgelehnt (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After und details.retry_after_seconds geben die Sekunden bis zum nächsten UTC-Tag an, und die Header RateLimit und X-RateLimit-* pro Minute entfallen). Eine kurze Ablehnung nur mit details.retry_after_seconds und Retry-After: 1 bedeutet eine gleichzeitige Aktualisierung des Zählers: Wiederholen Sie die Anfrage. Eine Ablehnung, die der Aufrufer nicht verschuldet hat, gibt die Einheiten zurück; mit 202 angenommene Arbeit, die später fehlschlägt, behält ihre Einheit. Eine weiche Sperre, keine Sicherheitsgrenze. Eine Einzelaufnahme, deren Idempotency-Key aus dem Speicher bedient wird (eine Wiederholung, 409 oder 422), wird vor der Tagesobergrenze bedient und kostet keine Einheit.\n\nCONCURRENT_CAPTURE_LIMIT: Zu viele Aufnahmen des Kontos laufen gleichzeitig (developer 10, startup 25, growth 100, legal_solo und legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` und Retry-After geben an, wann Sie es erneut versuchen können.\n\nTEST_MODE_DAILY_LIMIT: Arbeit im Testmodus (ein Testschlüssel oder eine Sitzung ohne verknüpfte Abrechnung) ist auf 200 Einheiten pro Konto und UTC-Tag begrenzt; `details.used`, `details.limit`, `details.reset_at`, die `X-Test-Daily-*`-Header und Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CAPTURE_FAILED (die Aufnahme konnte nicht gestartet werden; details.capture_id) oder INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "listCaptures",
        "summary": "Aufnahmen auflisten",
        "description": "Von Ihnen erstellte Einträge, neueste zuerst, in jedem Status (einschließlich gelöschter, also des Papierkorbs; filtern Sie mit status). Jeder Eintrag enthält source, redacted_from, label und compared, sodass sein Typ angezeigt werden kann, ohne ihn erneut zu lesen. Eine für ein Unternehmen erstellte Aufnahme wird nur aufgeführt, solange Sie noch darauf zugreifen dürfen (Sie haben einen Platz in diesem Unternehmen oder sind sein Inhaber); eine Seite kann daher weniger Einträge als limit enthalten, und next_cursor setzt nach dem zuletzt gelesenen Eintrag fort. Zertifikate, Vergleiche und Schwärzungen sind enthalten.\n\nSuche (nur eigene Liste): url, host oder schedule_id, kombiniert mit from / to und status, finden den Eintrag einer Seite an einem bestimmten Tag. Ein ungültiger Filter, zwei von url / host / schedule_id oder ein from, das nicht vor to liegt, ergibt 400 MALFORMED_REQUEST.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Seitengröße, 1 bis 200 (Standard 50). Ein Wert außerhalb dieses Bereichs ergibt 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor der vorherigen Seite (Format created_at|capture_id oder completed_at|capture_id mit order=completed_at). Unverändert übergeben.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Nur Einträge in diesem Status: processing, completed, failed oder deleted (Papierkorb). Jeder andere Wert ergibt 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Nur Aufnahmen genau dieser URL, so wie sie aufgenommen wurde (http oder https). Höchstens eines von url, host und schedule_id.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "Nur Aufnahmen, deren URL auf diesem Hostnamen liegt (ohne Beachtung der Groß-/Kleinschreibung, z. B. www.example.com; Subdomains sind separate Hosts). Höchstens eines von url, host und schedule_id. Einträge, die vor dem 2026-10-05 erstellt wurden, enthalten den Host, sobald das Backfill gelaufen ist.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "schedule_id",
            "in": "query",
            "required": false,
            "description": "Nur Aufnahmen, die von diesem Zeitplan erstellt wurden. Höchstens eines von url, host und schedule_id.",
            "schema": {
              "type": "string",
              "pattern": "^sch_[A-Za-z0-9]{8,32}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Erstellt zu oder nach diesem Zeitpunkt: ein Datum (YYYY-MM-DD, Mitternacht UTC) oder ein ISO-8601-Zeitstempel.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Erstellt vor diesem Zeitpunkt (exklusiv): ein Datum (YYYY-MM-DD, Mitternacht UTC) oder ein ISO-8601-Zeitstempel. Muss später als from liegen. Mit schedule_id oder url ist der neueste Eintrag vor einem Zeitpunkt die Version, die damals aktuell war.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "created_at (Standard: zuletzt erstellte zuerst) oder completed_at (zuletzt abgeschlossene zuerst; nur mit status=completed, sonst 400 MALFORMED_REQUEST). Mit completed_at begrenzen from und to den Wert completed_at, und next_cursor hat die Form completed_at|capture_id. Funktioniert mit url, host und schedule_id.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "completed_at"
              ],
              "default": "created_at"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eine Seite mit Aufnahmen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "captures": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "completed_at": {
                            "type": "string",
                            "nullable": true,
                            "description": "Zeitpunkt des Abschlusses der Aufnahme (null während der Verarbeitung oder nach einem Fehlschlag)."
                          },
                          "batch_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "schedule_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "test_mode": {
                            "type": "boolean",
                            "nullable": true
                          },
                          "external_archive": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/ExternalArchive"
                              }
                            ],
                            "description": "Nur abgeschlossene Einträge. Entspricht external_archive von GET /captures/{capture_id}."
                          },
                          "label": {
                            "type": "string",
                            "nullable": true,
                            "description": "Die Bezeichnung des Eintrags (Zertifikate: die vergebene Bezeichnung; Schwärzungen: welche Aufnahme sie schwärzen)."
                          },
                          "compared": {
                            "type": "array",
                            "nullable": true,
                            "items": {
                              "type": "string"
                            },
                            "description": "Nur Vergleiche: die IDs der beiden Quellaufnahmen."
                          },
                          "source": {
                            "type": "string",
                            "nullable": true,
                            "description": "Wie der Eintrag entstanden ist: api (eine Web-Aufnahme), schedule, certify-hash (ein Zertifikat), compare (ein Vergleich) oder redact (eine Schwärzung); null bei Einträgen, die älter als das Feld sind."
                          },
                          "redacted_from": {
                            "type": "string",
                            "nullable": true,
                            "description": "Nur Schwärzungen: die Aufnahme, aus der die Schwärzung erstellt wurde."
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Zeitpunkt der Aufnahme der Seite (abgeschlossene Einträge). Bei einer Schwärzung die Aufnahmezeit ihrer Quellaufnahme (redacted_at ist der Zeitpunkt der Schwärzung)."
                          },
                          "redacted_at": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Nur Schwärzungen: Zeitpunkt, zu dem die Schwärzung erstellt wurde. Das captured_at einer Schwärzung ist die Aufnahmezeit ihrer Quellaufnahme."
                          },
                          "compared_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Nur Vergleiche: Zeitpunkt, zu dem der Vergleich erstellt wurde. Sein captured_at enthält denselben Wert (für ältere Leser beibehalten); verwenden Sie compared_at."
                          },
                          "manifest_hash": {
                            "type": "string",
                            "nullable": true,
                            "description": "sha256:<hex> der manifest.json des Pakets (abgeschlossene Einträge)."
                          },
                          "private_note": {
                            "type": "string",
                            "nullable": true,
                            "description": "Ihre private Notiz zur Aufnahme (siehe PrivateNote), nie Teil des Beweispakets."
                          },
                          "legal_hold": {
                            "type": "boolean",
                            "description": "Die Aufnahme steht unter Löschsperre (wird nicht gelöscht, solange true; wird automatisch aufgehoben, wenn das Abonnement beendet ist und seine 30 Tage verstrichen sind). Siehe PATCH /captures/{capture_id}."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "null, wenn diese Seite weniger Einträge als limit enthält.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: ungültiger cursor, status (einer von processing, completed, failed, deleted), limit (1 bis 200) oder Suchfilter (url, host, schedule_id, from, to; höchstens eines von url / host / schedule_id; from vor to).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/batch": {
      "post": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "createBatch",
        "summary": "Mehrere URLs aufnehmen",
        "description": "Startet eine Aufnahme pro URL mit denselben Optionen. Jede Aufnahme ist unabhängig: Fragen Sie jede capture_id ab oder empfangen Sie einen Webhook pro Aufnahme. Verbraucht eine Rate-Limit-Einheit und eine Einheit des Monatslimits pro URL. Ein Batch mit mehr URLs, als ein Rate-Limit-Zeitfenster zulässt, kann nie erfolgreich sein und wird daher mit 400 RATE_LIMIT_COST_TOO_HIGH statt 429 abgelehnt. Teilen Sie ihn in kleinere Anfragen auf. Eine Wiederholung eines abgeschlossenen Batches (gleicher Idempotency-Key und gleicher Anfragetext) wird bedient, bevor das Rate-Limit angewendet wird, und kostet keine Rate-Limit-Einheit.\n\nRate-Limit pro Konto in 60-Sekunden-Zeitfenstern (alle API-Schlüssel des Kontos und seine Dashboard-Sitzung teilen sich ein Zeitfenster). Sobald der Limiter gelaufen ist, enthalten Antworten dieser Route die Header `RateLimit-Policy` und `RateLimit` (auf Basis von IETF draft-ietf-httpapi-ratelimit-headers) sowie die älteren `X-RateLimit-Limit`, `X-RateLimit-Remaining` und `X-RateLimit-Reset`. Eine 429 fügt `Retry-After` hinzu.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "urls": [
                  "https://example.com/a",
                  "https://example.com/b"
                ],
                "options": {
                  "case_reference": "Smith v. Jones"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Angenommen. Eine idempotente Wiederholung eines abgeschlossenen Batches mit demselben Idempotency-Key und Body liefert ebenfalls 202, mit `Idempotent-Replay: true`; sie wird bedient, bevor das Rate-Limit angewendet wird, und kostet daher keine Rate-Limit-Einheit.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_BATCH (details: reason und received oder index, url und ein verschachteltes reason-Objekt für die ungültige URL), MALFORMED_REQUEST oder ein Optionsfehler. WEBHOOK_REQUIRES_API_KEY: webhook_url aus einer Dashboard-Sitzung gesendet (Webhooks benötigen einen API-Schlüssel). RATE_LIMIT_COST_TOO_HIGH: mehr URLs, als ein Rate-Limit-Zeitfenster zulässt; details { reason, max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED oder PAYMENT_REQUIRED. Ein Batch wird auch mit QUOTA_EXCEEDED abgelehnt, wenn die monatliche Nutzung in diesem Moment nicht gelesen werden kann; wiederholen Sie die Anfrage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas mit einem Testschlüssel. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist. PLAN_ARCHIVE_ONLY: Der Tarif bewahrt nur das Archiv auf und erstellt keine neuen Vorgänge. PLAN_API_READ_ONLY: ein API-Schlüssel eines Website-Archive-Kontos (sein Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Website-Archive-Kontos, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge werden in diesem Tarif nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zu viele Anfragen im aktuellen 60-Sekunden-Zeitfenster. `error.details.retry_after_seconds` wiederholt Retry-After. RateLimit meldet r=0 und die Sekunden bis zum Zurücksetzen des Zeitfensters. Ebenfalls RATE_LIMITED bei einer Dashboard-Sitzung von Website Archive: deren Tagesobergrenze von 100 Einheiten pro UTC-Tag, gemeinsam genutzt von Einzel- und Batch-Aufnahmen (eine Einheit pro URL), compare, redact und certify-hash (eine Einheit pro Anfrage); eine Anfrage, die nicht hineinpasst, wird vollständig abgelehnt (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After und details.retry_after_seconds geben die Sekunden bis zum nächsten UTC-Tag an, und die Header RateLimit und X-RateLimit-* pro Minute entfallen). Eine kurze Ablehnung nur mit details.retry_after_seconds und Retry-After: 1 bedeutet eine gleichzeitige Aktualisierung des Zählers: Wiederholen Sie die Anfrage. Eine Ablehnung, die der Aufrufer nicht verschuldet hat, gibt die Einheiten zurück; mit 202 angenommene Arbeit, die später fehlschlägt, behält ihre Einheit. Eine weiche Sperre, keine Sicherheitsgrenze.\n\nCONCURRENT_CAPTURE_LIMIT: Keine URL des Batches konnte starten, weil das Konto bereits so viele Aufnahmen ausführt, wie es darf (developer 10, startup 25, growth 100, legal_solo und legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` und Retry-After. Passen nur einige hinein, wird der Batch mit 202 angenommen und listet den Rest in `not_admitted` auf.\n\nTEST_MODE_DAILY_LIMIT: Arbeit im Testmodus (ein Testschlüssel oder eine Sitzung ohne verknüpfte Abrechnung) ist auf 200 Einheiten pro Konto und UTC-Tag begrenzt, eine pro URL; `details.used`, `details.limit`, `details.reset_at`, die `X-Test-Daily-*`-Header und Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      }
    },
    "/captures/hosts": {
      "get": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "listCaptureHosts",
        "summary": "Aufgenommene Hostnamen auflisten",
        "description": "Jeder Hostname Ihrer eigenen Aufnahmeliste, mit der Anzahl der Aufnahmen und der letzten Aufnahmezeit pro Host, für einen Domainfilter: Übergeben Sie einen Host anschließend an GET /captures?host=. count ist die Anzahl der Einträge, die GET /captures?host=<host> ohne status liefert (unter den gelesenen Aufnahmen): Test- und Live-Aufnahmen gleichermaßen, jeder Status einschließlich der im Papierkorb (status deleted), so wie die Liste sie anzeigt; endgültig gelöschte Aufnahmen und Einträge ohne Seiten-URL (Zertifikate) werden ausgelassen. Eine Aufnahme zählt, sobald sie ihren gespeicherten Hostnamen enthält (Aufnahmen, die vor dem 2026-10-05 erstellt wurden, enthalten ihn, sobald das Backfill gelaufen ist). Eine Unternehmensaufnahme zählt nur, solange Sie noch darauf zugreifen dürfen (Sie haben einen Platz in diesem Unternehmen oder sind sein Inhaber). host ist in Kleinbuchstaben. Sortiert nach count (höchster zuerst), dann nach host (A bis Z). Höchstens 1.000 Hosts. Die neuesten 5.000 Aufnahmen werden gelesen; truncated ist true, wenn es mehr als 1.000 Hosts oder Aufnahmen gibt, die älter als diese 5.000 sind, sodass ein Host, der nur davor aufgenommen wurde, fehlen kann. Die Liste wird höchstens einmal alle 30 Sekunden pro Konto berechnet und kann bis zu 30 Sekunden alt sein (etwas älter, während eine andere Anfrage des Kontos sie berechnet). Die Berechnung kostet eine Einheit Ihres Rate-Limits pro Minute; eine aus diesem Cache bediente Antwort kostet nichts. Verfügbar in jedem Tarif, für API-Schlüssel und die Dashboard-Sitzung.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Die Hostnamen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "hosts",
                    "truncated"
                  ],
                  "properties": {
                    "hosts": {
                      "type": "array",
                      "maxItems": 1000,
                      "items": {
                        "type": "object",
                        "required": [
                          "host",
                          "count",
                          "last_captured_at"
                        ],
                        "properties": {
                          "host": {
                            "type": "string",
                            "example": "www.example.com",
                            "description": "Hostname in Kleinbuchstaben, der Wert, den GET /captures?host= erwartet."
                          },
                          "count": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 12,
                            "description": "Aufnahmen dieses Hosts unter den gelesenen Aufnahmen: die Einträge, die GET /captures?host= ohne status liefert, einschließlich derer im Papierkorb."
                          },
                          "last_captured_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "example": "2026-10-05T07:48:38Z",
                            "description": "Letzte Aufnahmezeit dieses Hosts (Zeitpunkt der Aufnahme der Seite, sonst Zeitpunkt der Anforderung der Aufnahme), UTC, auf die Sekunde genau."
                          }
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true, wenn die Liste unvollständig sein kann: mehr als 1.000 Hosts oder mehr Aufnahmen als die neuesten 5.000, die gelesen wurden."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: scope wird nicht unterstützt (nur eigene Aufnahmen).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "RATE_LIMITED: Das Rate-Limit pro Minute ist aufgebraucht (die Berechnung der Liste kostet eine Einheit), oder die Liste Ihres Kontos wird gerade von einer anderen Anfrage berechnet und es ist kein früheres Ergebnis gespeichert; wiederholen Sie die Anfrage nach Retry-After Sekunden.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "getCapture",
        "summary": "Aufnahme abrufen",
        "description": "Status und, nach Abschluss, eine frische package_url, die bis zu 24 Stunden gültig ist (siehe package_url_expires_at). Funktioniert für IDs von Aufnahmen, Zertifikaten, Vergleichen und Schwärzungen. Lesbar für das Konto, das sie erstellt hat, für Mitglieder des Mandats, zu dem sie gehört, und für den Inhaber des Unternehmens, für das sie erstellt wurde (wenn das Abonnement des Unternehmens beendet ist, bis sein Download-Zeitraum endet).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Senden Sie download, wenn Sie das Paket herunterladen: Bei einer Aufnahme in einem Mandat vermerkt dies capture.downloaded im Zugriffsprotokoll des Mandats. Reine Statusabfragen sollten es weglassen.",
            "schema": {
              "type": "string",
              "enum": [
                "download"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Die Aufnahme.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: nicht Ihre Aufnahme.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: unbekannte oder fehlerhafte ID, gelöschte Aufnahme, oder die Paketdatei wurde nicht gefunden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "deleteCapture",
        "summary": "Aufnahme löschen",
        "description": "Standard: verschiebt die Aufnahme in den Papierkorb. Sie kann bis restorable_until (30 Tage) mit PATCH wiederhergestellt werden; danach wird sie endgültig entfernt. Mit permanent=true wird das gespeicherte Paket sofort gelöscht und der Datensatz auf einen Tombstone-Eintrag reduziert; dies kann nicht rückgängig gemacht werden. Keine der beiden Varianten entfernt bereits heruntergeladene Pakete oder den OpenTimestamps-Nachweis. Eine Aufnahme in einem offenen oder archivierten Mandat wird zuerst aus dem Mandat entfernt (in dessen Beweiskettenprotokoll vermerkt), wenn der Aufrufer dieses Mandat öffnen kann; ein Aufrufer, der das nicht kann (ihr Urheber, nachdem er aus dem Mandat entfernt wurde), wird mit 409 MATTER_LINKED abgelehnt, für den Papierkorb und für permanent=true gleichermaßen, bis der Mandatsinhaber sie aus dem Mandat entfernt; eine Aufnahme in einem versiegelten Mandat kann nicht gelöscht werden. Eine Aufnahme, die noch verarbeitet wird, kann nicht gelöscht werden (409 CAPTURE_PROCESSING); löschen Sie sie, sobald sie abgeschlossen oder fehlgeschlagen ist. Das Löschen einer Aufnahme, die nie abgeschlossen wurde, gibt ihre Anrechnung auf das Monatslimit zurück.\n\nKarenzzeit nach Aufhebung der Löschsperre: 14 Tage lang nach Aufhebung einer Löschsperre (`deletion_blocked_until` in GET) wird die Aufnahme nicht endgültig gelöscht (409 CAPTURE_HOLD_GRACE mit `details.until`), weder durch eine Person noch durch die Bereinigung des Papierkorbs oder die Aufbewahrungsregel (die Löschung von Konto und Unternehmen nach Ende des Abonnements wartet nicht darauf). Das Verschieben in den Papierkorb ist erlaubt; dessen Bereinigung wartet bis zum Ende der Karenzzeit. Eine endgültige Löschung hebt zuerst eine noch auf den gespeicherten Objekten verbliebene S3-Object-Lock-Löschsperre auf; verweigert der Speicher dies, liefert die Anfrage 409 CAPTURE_LEGAL_HOLD (\"still locked in storage\", später erneut versuchen) und es wird nichts gelöscht. Jedes Verschieben in den Papierkorb, jede Wiederherstellung und jede endgültige Löschung wird in der Aufnahme-Ereigniskette des Kontos vermerkt (GET /captures/events). Eine Schwärzung oder ein Vergleich, deren Quellaufnahme unter Löschsperre steht, liefert `source_under_legal_hold: true`, wenn sie in den Papierkorb verschoben oder gelöscht werden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "permanent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Gelöscht. Papierkorb: { capture_id, status: \"deleted\", restorable: true, restorable_until }. Endgültig: { capture_id, status: \"deleted\", permanent: true }. Bereits im Papierkorb: { capture_id, status }.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restorable": {
                      "type": "boolean"
                    },
                    "restorable_until": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "permanent": {
                      "type": "boolean"
                    },
                    "source_under_legal_hold": {
                      "type": "boolean",
                      "description": "Nur vorhanden (true), wenn es sich um eine Schwärzung oder einen Vergleich handelt, die aus einer Aufnahme unter Löschsperre erstellt wurden."
                    },
                    "deletion_blocked_until": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Papierkorb während der Karenzzeit nach Aufhebung: Die Bereinigung des Papierkorbs wartet bis zu diesem Zeitpunkt."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: nicht Ihre Aufnahme. FORBIDDEN auch dann, wenn die Aufnahme zu einem nicht versiegelten Mandat gehört und der Aufrufer (der Unternehmensinhaber, der auf die Aufnahme eines Platzinhabers zugreift, nicht deren Urheber) dieses Mandat nicht öffnen kann. PLAN_API_READ_ONLY: ein API-Schlüssel eines Website-Archive-Kontos (sein Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED: Die Aufnahme ist ein Beweisstück eines versiegelten Mandats. MATTER_LINKED: Die Aufnahme ist ein Beweisstück eines Mandats, dem der Aufrufer nicht mehr angehört; der Mandatsinhaber entfernt sie zuerst. CAPTURE_PROCESSING: Die Aufnahme wird noch verarbeitet (error.details.status). CAPTURE_LEGAL_HOLD: Die Aufnahme steht unter Löschsperre und kann von niemandem gelöscht werden (weder in den Papierkorb verschoben noch endgültig gelöscht). CAPTURE_HOLD_GRACE: eine endgültige Löschung innerhalb der Karenzzeit nach Aufhebung der Löschsperre der Aufnahme (LEGAL_HOLD_RELEASE_GRACE_DAYS, 14 Tage); error.details.until gibt an, ab wann sie möglich ist. Das Verschieben in den Papierkorb ist während der Karenzzeit erlaubt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: Das gespeicherte Paket konnte nicht gelöscht werden; es wurde nichts gelöscht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "restoreCapture",
        "summary": "Gelöschte Aufnahme wiederherstellen, ihre private Notiz setzen oder ihre Löschsperre setzen bzw. aufheben",
        "description": "Stellt eine Aufnahme aus dem Papierkorb wieder her. Wurde sie beim Löschen aus einem Mandat entfernt, wird sie wieder hinzugefügt, wenn dieses Mandat noch offen ist und der Aufrufer es noch öffnen kann; matter_id und matter_relinked sind nur dann in der Antwort enthalten (matter_id null, wenn die erneute Verknüpfung nicht erfolgt ist). Ein Aufrufer, der dieses Mandat nicht mehr öffnen kann, erhält die Aufnahme ohne das Mandat zurück, im Beweiskettenprotokoll des Mandats wird nichts vermerkt, und die Antwort erwähnt das Mandat nicht. Eine Aufnahme, die nicht im Papierkorb liegt, wird unverändert zurückgegeben.\n\nPrivate Notiz: Ein Anfragetext mit `private_note` (eine Zeichenkette von höchstens 200 Zeichen oder null zum Löschen) setzt stattdessen die private Notiz der Aufnahme und bewirkt sonst nichts; er kann nicht mit `action` kombiniert werden. Die Notiz wird nur im Datensatz der Aufnahme gespeichert, außerhalb des versiegelten Beweispakets, sodass ihre Änderung nie den Beweis oder seine Hashes verändert. Nur der Ersteller der Aufnahme darf sie setzen (der Unternehmensinhaber und Mandatsmitglieder erhalten 403), bei einer Aufnahme in jedem Zustand außer einer endgültig gelöschten (404). Die Antwort ist { capture_id, status, private_note }.\n\nLöschsperre: Ein Anfragetext `{ \"action\": \"legal_hold\", \"enabled\": true|false, \"reason\"?: string }` setzt (true) oder hebt (false) die Löschsperre der Aufnahme auf und bewirkt sonst nichts. Eine Aufnahme unter Löschsperre wird nicht gelöscht: DELETE (Papierkorb oder endgültig) liefert für alle 409 CAPTURE_LEGAL_HOLD, und weder die Bereinigung des Papierkorbs noch die Löschung nach Aufbewahrungsfrist berührt sie; GET zeigt `storage.retention.earliest_deletion_at` als null. Eine Löschsperre bleibt nur bestehen, solange das Konto einen Tarif hat, der Daten aufbewahrt (jeder kostenpflichtige Tarif, Archive only eingeschlossen; bei einer Unternehmensaufnahme: das Unternehmen ist aktiv oder wird als Firmenarchiv geführt). Endet das Abonnement, bleiben die Aufnahmen 30 Tage lang verfügbar; wird bis dahin kein solcher Tarif gebucht, wird an diesem Datum jede Löschsperre des Kontos (oder des Unternehmens) automatisch aufgehoben (Akteur system, Grund subscription_ended, vermerkt wie jede Aufhebung, ohne Karenzzeit), und die Aufnahmen werden mit den übrigen gelöscht. Um gesperrte Aufnahmen zu behalten, wechseln Sie vor diesem Datum zu Archive only (oder buchen Sie das Firmenarchiv) oder laden Sie sie herunter. In diesen 30 Tagen kann keine neue Löschsperre gesetzt werden (das Konto hat keinen kostenpflichtigen Tarif), bestehende Löschsperren können weiterhin aufgehoben werden. Die Löschsperre wird nur im Datensatz der Aufnahme gespeichert, außerhalb des versiegelten Beweispakets. `reason` ist optionaler reiner Text von höchstens 200 Zeichen (keine Steuerzeichen, unsichtbaren Zeichen oder Textrichtungszeichen; Leerraum wird zusammengefasst, leer bedeutet keiner); er wird mit der Löschsperre gespeichert und beim Aufheben der Löschsperre gelöscht.\n\nWer: Eine Löschsperre setzen darf jeder, der auf die Aufnahme zugreifen darf (ihr Urheber, bei einer Unternehmensaufnahme solange er noch einen Platz in diesem Unternehmen hat, und der Unternehmensinhaber, auch bei einem Unternehmen, dessen Abonnement beendet ist, solange es sich in seiner Aufbewahrungsfrist befindet oder als Firmenarchiv geführt wird; das Archiv nur über die Dashboard-Sitzung). Aufheben: bei einer persönlichen Aufnahme ihr Urheber, bei einer Unternehmensaufnahme nur der Unternehmensinhaber (ein Platzinhaber erhält 403 LEGAL_HOLD_OWNER_ONLY). Das Setzen einer Löschsperre erfordert einen kostenpflichtigen Tarif (jeder kostenpflichtige Tarif, Archive only eingeschlossen): Die kostenlose Sandbox, ebenso ein Konto, dessen Abonnement beendet ist, erhält 403 LEGAL_HOLD_NOT_AVAILABLE mit details.reason \"plan\" (ein laufendes Firmenarchiv gilt für die Aufnahmen dieses Unternehmens als kostenpflichtig, über die Dashboard-Sitzung seines Inhabers), und eine Aufnahme im Testmodus (mit Wasserzeichen, erstellt mit einem Testschlüssel oder ohne verknüpfte Abrechnung) erhält 403 LEGAL_HOLD_NOT_AVAILABLE mit details.reason \"test_capture\". Das Aufheben einer Löschsperre funktioniert in jedem Tarif, auch nach Ende des Abonnements. Bei Website Archive ist der API-Schlüssel schreibgeschützt (403 PLAN_API_READ_ONLY), die Dashboard-Sitzung darf es. Ein überfälliges oder unbezahltes Abonnement blockiert es nicht. Eine Dashboard-Sitzung oder ein Schlüssel, deren E-Mail-Adresse nicht bestätigt ist, erhält 403 EMAIL_NOT_VERIFIED.\n\nAbgelehnt bei einer Aufnahme im Papierkorb (409 CAPTURE_IN_TRASH: zuerst wiederherstellen) oder einer, die noch verarbeitet wird (409 CAPTURE_PROCESSING); eine endgültig gelöschte Aufnahme oder eine, deren endgültige Löschung im Gange ist, ergibt 404. Jede Löschsperre-Anfrage kostet eine Einheit des Rate-Limits pro Minute des Kontos (429 RATE_LIMITED mit Retry-After, wenn es aufgebraucht ist). Wird der Zustand gesendet, den die Aufnahme bereits hat, ändert sich nichts, und es wird 200 mit den gespeicherten Feldern und `changed: false` geliefert. Jede Änderung wird als capture.legal_hold im Audit-Log erfasst (bei einer Unternehmensaufnahme im Mandanten des Unternehmens), im Zugriffsverlauf der Aufnahme vermerkt (`legal_hold_on` / `legal_hold_off`, bleibt erhalten, wenn die Aufnahme später gelöscht wird) und, wenn die Aufnahme zu einem Mandat gehört, als capture.legal_hold_placed oder capture.legal_hold_released mit der Aufnahme-ID als Ziel in das Beweiskettenprotokoll des Mandats geschrieben (der Grund geht in das Zugriffsprotokoll des Mandats, nie in die gehashte Kette; die Kette eines versiegelten Mandats ändert sich nicht). Die Antwort ist { capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }.\n\nLöschsperre v2: Das Setzen oder Aufheben einer Löschsperre setzt bzw. hebt auch eine S3-Object-Lock-Löschsperre auf jeder Version des gespeicherten Pakets, seinem aktualisierten OpenTimestamps-Nachweis und dem separat gespeicherten Duplikat (`s3_legal_hold` in GET; verweigert der Speicher dies oder hat er keine Object-Lock-Konfiguration, gilt die Löschsperre im Datensatz der Aufnahme dennoch, und ein fehlgeschlagener Versuch wird vom Server wiederholt). Eine Aufhebung startet eine Karenzzeit von 14 Tagen, in der die Aufnahme nicht endgültig gelöscht wird (nicht nach Ende des Abonnements: Dann treten die 30 Tage nach dem Ende an ihre Stelle). Eine durch die Löschsperre ihres Mandats gesetzte Löschsperre (`legal_hold_source` \"matter:<id>\") wird am Mandat aufgehoben, solange diese Löschsperre besteht: Eine Aufhebung hier ergibt 409 LEGAL_HOLD_MATTER (error.details.matter_id für einen Leser des Mandats). Jede Änderung wird in der Aufnahme-Ereigniskette des Kontos vermerkt (GET /captures/events).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "restore (der Standard für einen Anfragetext ohne private_note) oder legal_hold (mit enabled und dem optionalen reason).",
                    "enum": [
                      "restore",
                      "legal_hold"
                    ]
                  },
                  "private_note": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PrivateNote"
                      }
                    ],
                    "description": "Die private Notiz setzen (eine Zeichenkette) oder löschen (null oder eine leere Zeichenkette). Kann nicht mit action kombiniert werden."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Mit der Aktion legal_hold (dort erforderlich): true setzt die Löschsperre, false hebt sie auf."
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 200,
                    "description": "Mit der Aktion legal_hold, optional: warum die Aufnahme gesperrt ist, reiner Text von höchstens 200 Zeichen. Wird mit der Löschsperre gespeichert, außerhalb des Beweispakets; beim Aufheben ignoriert (er geht dann nur in das Audit-Log)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wiederhergestellt ({ capture_id, status, restored: true, matter_id?, matter_relinked? }), unverändert ({ capture_id, status }), private Notiz gesetzt ({ capture_id, status, private_note }) oder Löschsperre gesetzt ({ capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restored": {
                      "type": "boolean"
                    },
                    "matter_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "matter_relinked": {
                      "type": "boolean"
                    },
                    "private_note": {
                      "type": "string",
                      "nullable": true
                    },
                    "legal_hold": {
                      "type": "boolean",
                      "description": "Löschsperren-Aktion: die Löschsperre der Aufnahme nach der Anfrage."
                    },
                    "legal_hold_set_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Löschsperren-Aktion: Zeitpunkt, zu dem die aktuelle Löschsperre gesetzt wurde (null, wenn keine besteht)."
                    },
                    "legal_hold_reason": {
                      "type": "string",
                      "nullable": true,
                      "description": "Löschsperren-Aktion: der mit der aktuellen Löschsperre gespeicherte Grund oder null."
                    },
                    "changed": {
                      "type": "boolean",
                      "description": "Löschsperren-Aktion: false, wenn die Aufnahme bereits den angeforderten Zustand hatte (nichts wurde geändert oder protokolliert)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: ungültiges JSON, ein Anfragetext, der kein Objekt ist, eine andere action als restore oder legal_hold, private_note kombiniert mit action, oder eine private_note, die keine Zeichenkette und nicht null ist, länger als 200 Zeichen ist oder Steuerzeichen, unsichtbare Zeichen oder Textrichtungszeichen enthält. Für legal_hold: enabled fehlt oder ist kein Boolean, ein reason, der keine Zeichenkette ist, länger als 200 Zeichen ist oder kein reiner Text ist, oder ein unbekanntes Feld.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: nicht Ihre Aufnahme. FORBIDDEN auch dann, wenn die Aufnahme zu einem nicht versiegelten Mandat gehört und der Aufrufer (der Unternehmensinhaber, der auf die Aufnahme eines Platzinhabers zugreift, nicht deren Urheber) dieses Mandat nicht öffnen kann. PLAN_API_READ_ONLY: ein API-Schlüssel eines Website-Archive-Kontos (sein Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). Für private_note: FORBIDDEN für alle außer dem Ersteller der Aufnahme (der weiterhin auf sie zugreifen dürfen muss). Für legal_hold: FORBIDDEN, wenn der Aufrufer nicht auf die Aufnahme zugreifen darf; LEGAL_HOLD_OWNER_ONLY, wenn ein Platzinhaber (nicht der Unternehmensinhaber) die Löschsperre einer Unternehmensaufnahme aufhebt; LEGAL_HOLD_NOT_AVAILABLE beim Setzen einer Löschsperre ohne kostenpflichtigen Tarif (details.reason plan) oder bei einer Aufnahme im Testmodus (details.reason test_capture); EMAIL_NOT_VERIFIED bei einer nicht bestätigten E-Mail-Adresse. Die obige Mandatsregel gilt nicht für eine Löschsperre.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND, einschließlich einer endgültig gelöschten Aufnahme.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Nur Löschsperre. CAPTURE_IN_TRASH: Die Aufnahme liegt im Papierkorb; stellen Sie sie zuerst wieder her. CAPTURE_PROCESSING: Die Aufnahme wird noch verarbeitet (error.details.status). LEGAL_HOLD_MATTER: Die Löschsperre der Aufnahme wurde durch die Löschsperre ihres Mandats gesetzt, die noch besteht; heben Sie sie am Mandat auf. LEGAL_HOLD_SOURCE: die Löschsperre einer Schwärzung oder eines Vergleichs, die einer Aufnahme folgt, aus der sie erstellt wurden (bei einem Vergleich einer der beiden verglichenen Aufnahmen), die noch unter Löschsperre steht (an dieser Aufnahme oder über ihr Mandat gesetzt): Das Paket bleibt gesperrt, bis diese Löschsperre aufgehoben wird (`details.source_capture_id`). Ein Vergleich ist gesperrt, solange eine der verglichenen Aufnahmen gesperrt ist, und wird erst freigegeben, wenn keine mehr gesperrt ist (`legal_hold_source` \"derived:<capture id>\" nennt die gesperrte).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Nur Löschsperre. RATE_LIMITED: Das Rate-Limit pro Minute des Kontos ist aufgebraucht. `error.details.retry_after_seconds` wiederholt Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/verify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "verifyCapture",
        "summary": "Gespeichertes Paket prüfen",
        "description": "Liest das gespeicherte Paket und prüft die RSA-Signatur von manifest.json und den SHA-256 jeder darin aufgeführten Datei. Pakete größer als 256 MiB liefern 413 VERIFY_PACKAGE_TOO_LARGE; prüfen Sie diese offline mit den beiliegenden Skripten. Jede Anfrage kostet eine Einheit des Rate-Limits pro Minute des Kontos (429 RATE_LIMITED). Enthält das Paket manifest.json.tsr, wird auch das eIDAS-Zeitstempel-Token von ProofSnap geprüft (checks.eidas): die Signatur und die signierten Attribute des Tokens, dass es den SHA-256 von manifest.json enthält, die extendedKeyUsage des TSA-Zertifikats, seine Zertifikatskette bis zur fest hinterlegten Root-CA Disig QCA4 zum Zeitpunkt des Tokens (genTime) und der Status des TSA-Zertifikats aus der OCSP-Antwort oder CRL, die bei der Ausstellung in eidas_validation.json vermerkt wurde (per Signatur geprüft, nicht live abgerufen). Diese Prüfung fordert nie einen neuen Zeitstempel an. Sie meldet außerdem, ob manifest.json.ots vorhanden ist und ob der Manifest-Hash im ProofSnap-Ausstellungsregister steht. Das Ergebnis hängt nur von der Manifest-Signatur und den aufgeführten Dateien ab; ein Token, das seine Prüfung nicht besteht, wird in checks.eidas und in warnings gemeldet (EIDAS_TOKEN_FAILED), nicht in das Ergebnis eingerechnet. Ein mit qualifiziertem Zeitstempel ausgestelltes Paket vermerkt eidas_requested: true in seiner signierten manifest.json; enthält ein solches Paket keine manifest.json.tsr mehr, werden checks.eidas und checks.eidas_disig als fehlend gemeldet, mit der Warnung EIDAS_TOKEN_MISSING (die beiliegenden Prüfskripte melden für dasselbe Paket FAIL). Erlaubt für den Inhaber der Aufnahme, Mitglieder ihres Mandats und den Inhaber des Unternehmens, für das sie erstellt wurde. Um die Zeitstempel unabhängig zu prüfen, führen Sie die Skripte im Ordner verification/ des Pakets aus.\n\nImportierte Pakete (source import): Die gespeicherte ZIP wird mit den Importprüfungen erneut geprüft (Signatur, jede Datei, Beweiskette, forensische Kette, Zeitstempel; die Herkunft wird für API-Pakete erneut im API-Register nachgeschlagen und für Pakete der Erweiterung aus dem Importdatensatz übernommen). Die Antwort ist { capture_id, source, type: \"import\", package_kind, verifier: \"package-verify\", verdict (match, modified, invalid), rechecked: true, manifest_sha256, zip_sha256, zip_matches_import, kind, summary, levels, checks, reasons, provenance }. Ein Paket über 512 MiB oder eine Prüfung, die nicht in etwa 20 Sekunden abgeschlossen wird, liefert rechecked: false mit einem Grund (too_large_for_synchronous_check, time_budget, storage_error), verdict: null und stored_verification (der beim Import vermerkte Bericht).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Prüfergebnis.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResult"
                },
                "example": {
                  "verdict": "match",
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "source": "s3",
                  "checks": {
                    "manifest_signature": {
                      "status": "valid",
                      "algorithm": "RSA-4096"
                    },
                    "declared_files": {
                      "status": "all_match"
                    },
                    "bitcoin_ots": {
                      "status": "present"
                    },
                    "eidas_disig": null,
                    "proofsnap_issuance": {
                      "status": "issued_by_proofsnap",
                      "manifest_sha256": "3f2a...",
                      "issued_at": "2026-09-01T10:00:00.000Z",
                      "type": "capture"
                    },
                    "eidas": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: unbekannt, gelöscht oder kein gespeichertes Paket.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "VERIFY_PACKAGE_TOO_LARGE: Das gespeicherte Paket ist größer als 256 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Das Rate-Limit pro Minute des Kontos ist aufgebraucht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: Das gespeicherte Paket konnte nicht gelesen werden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/redact": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Vergleichen und Schwärzen"
        ],
        "operationId": "redactCapture",
        "summary": "Geschwärzte Kopie erstellen",
        "description": "Erstellt aus einer abgeschlossenen Aufnahme ein neues Paket, aus dem die ausgewählten Inhalte entfernt sind, signiert es neu (neuer RSA-4096-Schlüssel) und reicht es zur Verankerung mit OpenTimestamps ein. Das Original wird nicht verändert. Synchron: liefert 201, sobald das Paket fertig ist. Wird als eine Aufnahme abgerechnet.\n\n- Box-Schwärzungen schwärzen Bereiche des Screenshots. Ein JPEG-Screenshot in der Form, in der die Aufnahme ihn schreibt (sequenziell, Graustufen oder YCbCr), wird geschwärzt, ohne ihn in Pixel zu dekodieren: Die komprimierten Blöcke (MCUs, 8 bis 32 Pixel Kantenlänge), die eine Box berührt, werden durch schwarze Blöcke ersetzt, alle anderen Blöcke bleiben unverändert, sodass der Rest des Bildes nicht neu komprimiert wird (`screenshot_method` `jpeg_dct_blocks`, bis 150 Megapixel). Der geschwärzte Bereich ist jede Box, auf ganze Blöcke erweitert (`painted_rects`), er enthält die Box also immer. Ein PNG-Screenshot oder ein JPEG anderer Art wird dekodiert, übermalt und als screenshot.jpeg neu kodiert; anschließend werden die 8 x 8-Blöcke, die eine Box berührt, in der neuen Datei geschwärzt (`screenshot_method` `decode_repaint_jpeg`, bis 60 Megapixel). Ein Screenshot über seinem Limit oder mit unlesbaren Bilddaten wird mit 422 REDACTION_SCREENSHOT_UNREADABLE abgelehnt, bevor etwas berechnet wird. Ist der Header lesbar, die Bilddaten aber nicht, wird der Fehler erst erkannt, nachdem die monatliche Einheit reserviert wurde, und diese Einheit wird wieder freigegeben. Text- und Metadaten-Schwärzungen funktionieren auch ohne Boxen. redaction_manifest.json (`screenshot_boxes`) hält die Methode, `painted_rects`, das Blockraster und einen erläuternden Hinweis für forensische Gutachter fest.\n- Text-Schwärzungen entfernen Treffer aus page.html und domtextcontent.txt, aus jedem Freitextwert von metadata.json (Seitentitel und Meta-Werte, WHOIS-/RDAP-Einträge, das TLS-Subject, Werte der HTTP-Antwort-Header) sowie aus jedem Freitextwert von forensic_log.json und chain_of_custody.json (case_reference, case_id, case_note, Ihre Metadatenwerte, injizierter Code und dessen Fehler, die Beschriftungen geschlossener Pop-ups, legal_context.note). Kennungen, Hashes, Zeitangaben und URLs in diesen Einträgen bleiben unverändert. Auch kodierte Vorkommen werden entfernt: HTML-Zeichenreferenzen (benannt, dezimal, hexadezimal, etwa M&uuml;ller oder jan&#64;firma.cz), Prozent-Kodierung (mailto:jan%40firma.cz) und JavaScript-Escapes (\\u0040, \\x40), auch bei bis zu zwei Ebenen tief verschachtelter Kodierung; die Markierung ersetzt dabei die kodierten Zeichen selbst. Die Prüfung auf verbliebene Treffer nach der Schwärzung liest die JSON-Dateien Wert für Wert und durchsucht jeden Text sowohl in der geschriebenen als auch in der dekodierten Form, sie schlägt also bei einem Treffer in jeder dieser Formen fehl. Text-Schwärzungen schwärzen außerdem den Screenshot dort, wo die text_layout.json der Quellaufnahme ein Vorkommen verortet (`text_rule_rects`): Übermalt werden die geschätzten Zeilen des Textblocks, die den Treffer enthalten, und der ganze Block nur dann, wenn die Schätzung unsicher ist (gezählt in `screenshot_boxes.from_text_rules.rects_whole_block` von redaction_manifest.json); Vorkommen, die sie nicht verortet, werden in `text_occurrences_not_located` gezählt. Regeln berücksichtigen die exakte Groß- und Kleinschreibung, sofern `case_insensitive` nicht gesetzt ist.\n- Metadaten-Schwärzungen entfernen Felder aus metadata.json. Zwei Namen reichen weiter. `ip_dns` (ebenfalls akzeptiert: `ip`, `dns`, `resolved_ips`) entfernt die IP-Adressen und DNS-Antworten der Zielseite aus metadata.json, forensic_log.json und chain_of_custody.json (die eigenen Adressen des Aufnahmerechners bleiben erhalten) und entfernt sie, nur als vollständige Adressen, auch aus page.html, domtextcontent.txt und dem Screenshot als automatische Textregeln (erfasst nur mit der Quelle ip_dns, niemals mit der Adresse oder einem Hash davon, und nicht auf das Limit von text_redactions angerechnet). `cookie_names` (auch `cookie`, `cookies`, `http_headers.cookie`) entfernt die Header Cookie und Set-Cookie sowie jede Liste von Cookie-Namen in diesen Einträgen; die Anzahlen bleiben erhalten. Cookie-Werte sind in jeder Aufnahme bereits geschwärzt.\n\nEine Box deckt nur Pixel ab. Text unter einer Box bleibt in page.html, domtextcontent.txt und metadata.json erhalten, sofern Sie nicht zusätzlich eine Text-Schwärzung hinzufügen, und ein Bild unter einer Box kann in page.html weiterhin referenziert oder eingebettet sein (img src oder srcset, CSS url(), eine data:-URI); eine Text-Schwärzung, die seine Adresse trifft, entfernt diese Referenz. Das geschwärzte Paket übernimmt aus dem Original nur screenshot.jpeg (oder screenshot.png, wenn keine Box ihn ersetzt hat), page.html, domtextcontent.txt, metadata.json und forensic_log.json, dazu die Dateien, die es neu schreibt (evidence.pdf, chain_of_custody.json, redaction_manifest.json, die Prüfdateien und die Manifest-Dateien). Jede andere Datei des Originals, etwa images/, image_inventory.json, text_layout.json, page.mhtml, network.har und capture.warc, wird weggelassen und in redaction_manifest.json unter dropped_for_leak_protection aufgeführt. Text-Schwärzungen werden auch auf diese Dateinamen angewendet, weil eine Bildkopie nach ihrer URL benannt ist. chain_of_custody.json behält die Ereignisse des Originals zu diesen Dateien, markiert als artifact_removed_in_redaction und ohne die Hashes von Dateien, die das geschwärzte Paket nicht enthält. Eine rule_902_certification.pdf des Originals wird ebenfalls entfernt, weil sie die Dateien und Hashes des Originals aufführt; die Entfernung wird in redaction_manifest.json (removed_as_stale) und in chain_of_custody.json aufgeführt. Die Aufnahme-URL wird nie geschwärzt, weil sie Teil des signierten Manifests ist: Eine Text-Schwärzung, deren Text auch in der Aufnahme-URL oder der finalen URL vorkommt, wird überall sonst angewendet, der Text bleibt in dieser URL stehen, und die Regel wird in `rules_in_capture_url` und in redaction_manifest.json (leakage_check.capture_url) aufgeführt. Nur Aufnahmen von Webseiten können geschwärzt werden (ein Vergleich, eine Zertifizierung oder eine Schwärzung wird mit 422 REDACTION_SOURCE_UNSUPPORTED abgelehnt).\n\nEine Schwärzung einer Testaufnahme (erstellt mit einem Testschlüssel, in der Sandbox oder von einem Konto ohne verknüpftes Abrechnungskonto) ist ein Testpaket, unabhängig davon, welcher Schlüssel sie anfordert: mit Wasserzeichen, `test_mode: true` in manifest.json, ein Testeintrag (key_env test), der nicht abgerechnet wird und keine monatliche Einheit verbraucht. include_eidas bei einer solchen Schwärzung wird mit 403 EIDAS_REQUIRES_LIVE_KEY (details.reason) abgelehnt.\n\nDer Schwärzungsbericht (evidence.pdf) wird in `language` erstellt (Standard: die Sprache der Quellaufnahme), einschließlich Hauptteil, Deckblatt und Testbanner.\n\nBevor etwas geschwärzt wird, wird jede Datei, die die Schwärzung liest oder übernimmt (der Screenshot, page.html, domtextcontent.txt, metadata.json, forensic_log.json, chain_of_custody.json, text_layout.json) und die das Quellmanifest mit einem SHA-256 aufführt, neu gehasht (redaction_manifest.json derived_from.source_files_rehashed); eine Abweichung oder eine fehlende Datei liefert 422 REDACTION_SOURCE_INTEGRITY_FAILED, und es wird nichts ausgestellt, sodass eine beschädigte oder veränderte Quelle nie unter einer neuen Signatur erneut versiegelt wird. Dateien, die das geschwärzte Paket weglässt, werden nicht gelesen. Eine synchrone Schwärzung, deren Quelle zu lange zum Lesen brauchte, bricht ab, bevor etwas reserviert wird, mit 500 CAPTURE_TIME_BUDGET_EXCEEDED.\n\nEine Schwärzung einer Aufnahme unter Löschsperre steht ebenfalls unter Löschsperre (legal_hold_source \"derived:<capture id>\"), ebenso jede Schwärzung einer Aufnahme, sobald auf diese eine Löschsperre gesetzt wird; das Aufheben der Löschsperre der Aufnahme hebt auch diese Sperren auf, und solange die Aufnahme gesperrt bleibt, können sie nicht einzeln aufgehoben werden (409 LEGAL_HOLD_SOURCE). captured_at eines Schwärzungseintrags ist der Aufnahmezeitpunkt seiner Quelle; redacted_at ist der Zeitpunkt, zu dem die Schwärzung erstellt wurde.\n\nDie Schwärzung schlägt sicher fehl (fail closed): Findet die Prüfung nach der Schwärzung einen geschwärzten Begriff noch irgendwo im Paket außerhalb der Aufnahme-URL und der finalen URL, wird kein Paket ausgestellt, und die Anfrage liefert 422 REDACTION_LEAK_DETECTED (`error.details` `{ files, rule_indexes, metadata_fields, in_capture_url }`; der Begriff selbst wird nie zurückgegeben). redaction_manifest.json hält `leakage_check.fail_closed: true` fest.\n\nWerte werden an Zeichengrenzen gekürzt (ein Zeichen wird nie geteilt): Suchtexte auf 2.000 Zeichen, Begründungen auf 200, reason_summary auf 500, custodian-Felder auf 200. Begründungen, reason_summary und custodian-Felder werden auf das Schwärzungszertifikat gedruckt und müssen reiner Text sein: Ein Steuerzeichen, unsichtbares Zeichen oder Textrichtungszeichen wird mit 400 abgelehnt.\n\nZeitbudget und Wiederholungen: Die Anfrage antwortet innerhalb des 29-Sekunden-Limits von API Gateway. Die Versiegelung läuft unter einem Zeitbudget, das ab der Anfrage gezählt wird; würde der qualifizierte Zeitstempel (include_eidas) nicht in das Budget passen, wird er nicht angefordert, und das Paket wird nur mit dem OpenTimestamps-Anker ausgeliefert (timestamps.eidas status not_obtained, reason time_budget, nicht berechnet), und eine OpenTimestamps-Anfrage, die nicht hineinpasst, wird weggelassen (chain_of_custody.json hält ots_skipped mit reason time_budget fest). Senden Sie einen Idempotency-Key, damit eine Wiederholung (etwa nach einem 504 von einem Gateway oder einem Netzwerkfehler) dieselbe Schwärzung zurückgibt, statt eine zweite zu erstellen und abzurechnen. Eine Wiederholung, während die erste Anfrage noch läuft, liefert 409 IDEMPOTENCY_IN_PROGRESS mit der Schwärzungs-ID in `error.details.capture_id` (fragen Sie damit GET /captures/{capture_id} ab). Eine spätere Wiederholung liefert 201 mit dem ursprünglichen Body und `Idempotent-Replay: true`. Eine fehlgeschlagene Schwärzung gibt ihren Key frei, sodass eine Wiederholung mit demselben Key erneut ausgeführt wird.\n\nRate-Limit pro Konto in 60-Sekunden-Fenstern (alle API-Schlüssel des Kontos und seine Dashboard-Sitzung teilen sich ein Fenster). Sobald der Limiter gelaufen ist, enthalten Antworten dieses Endpunkts die Header `RateLimit-Policy` und `RateLimit` (basierend auf IETF draft-ietf-httpapi-ratelimit-headers) sowie die älteren `X-RateLimit-Limit`, `X-RateLimit-Remaining` und `X-RateLimit-Reset`. Eine 429 ergänzt `Retry-After`.\n\nEin importiertes Beweispaket (source import, IDs mit dem Präfix `imp_`) kann nicht geschwärzt werden: 422 IMPORT_NOT_SUPPORTED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "boxes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "minimum": 0
                        },
                        "y": {
                          "type": "number",
                          "minimum": 0
                        },
                        "w": {
                          "type": "number",
                          "minimum": 0
                        },
                        "h": {
                          "type": "number",
                          "minimum": 0
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "x",
                        "y",
                        "w",
                        "h"
                      ]
                    },
                    "maxItems": 200,
                    "description": "Rechtecke in Pixeln des Screenshots der Quellaufnahme (x und y ab dessen linker oberer Ecke, w und h für Breite und Höhe). Liegen alle vier Werte einer Box zwischen 0 und 1, werden sie als Anteile an Breite und Höhe des Screenshots gelesen. Eine Box, die vollständig außerhalb des Screenshots liegt, übermalt nichts und wird in ignored_boxes aufgeführt; eine Box, die über einen Rand hinausragt, wird am Rand abgeschnitten und in clipped_boxes aufgeführt. Fällt jede Box außerhalb und ändert keine Text- oder Metadaten-Schwärzung etwas, wird die Anfrage mit 400 REDACTION_NO_OPS abgelehnt, bevor etwas berechnet wird."
                  },
                  "text_redactions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "match": {
                          "oneOf": [
                            {
                              "type": "string",
                              "description": "Wörtlicher Text.",
                              "minLength": 1
                            },
                            {
                              "type": "object",
                              "properties": {
                                "pattern": {
                                  "type": "string",
                                  "description": "Regulärer Ausdruck, 1 bis 500 Zeichen. Muster mit verschachtelten Quantoren wie (a+)+ werden mit 400 abgelehnt.",
                                  "minLength": 1,
                                  "maxLength": 500
                                },
                                "flags": {
                                  "type": "string",
                                  "description": "Beliebige von g, i, m, s, u, jedes höchstens einmal. y (sticky) wird mit 400 abgelehnt: Es würde nur an einer Position treffen und andere Vorkommen stehen lassen.",
                                  "pattern": "^[gimsu]*$"
                                }
                              },
                              "required": [
                                "pattern"
                              ]
                            }
                          ]
                        },
                        "case_insensitive": {
                          "type": "boolean",
                          "default": false,
                          "description": "Einen wörtlichen String ohne Berücksichtigung der Groß- und Kleinschreibung abgleichen (für einen regulären Ausdruck das Flag i verwenden)."
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200,
                          "description": "Reiner Text, wird auf das Schwärzungszertifikat gedruckt."
                        }
                      },
                      "required": [
                        "match"
                      ]
                    },
                    "maxItems": 200
                  },
                  "metadata_fields": {
                    "type": "array",
                    "description": "Feldpfade in metadata.json (ein Segment `*` trifft jedes Array-Element) oder die Namen `ip_dns`, `cookie_names` und `authorization` (siehe oben).",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "Feldpfad in metadata.json."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "reason": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field"
                          ]
                        }
                      ]
                    },
                    "maxItems": 100
                  },
                  "reason_summary": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Reiner Text, wird auf das Schwärzungszertifikat gedruckt."
                  },
                  "custodian": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string"
                      }
                    },
                    "description": "Wird auf den Schwärzungsbericht gedruckt. name und title folgen der Regel für reinen Text (siehe PlainText)."
                  },
                  "language": {
                    "type": "string",
                    "description": "Sprache der menschenlesbaren PDFs. Einer der Werte: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Groß- und Kleinschreibung, Bindestrich und Region werden toleriert („DE“, „fr-FR“ und „PT-BR“ werden akzeptiert); ein bloßes „pt“ oder „zh“ wird abgelehnt. Ein nicht unterstützter Wert liefert 400 MALFORMED_REQUEST. Optional: Ohne Angabe (oder leer) ist der Bericht in der Sprache der Quellaufnahme."
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Wie bei CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "https-URL, die redaction.completed empfängt. Das Ereignis wird direkt nach der Antwort 201 gesendet, nicht davor. Die URL muss https verwenden und darf nicht auf eine private, Loopback-, Link-Local-, Carrier-Grade-NAT- oder Cloud-Metadaten-Adresse zeigen (dieselben Regeln wie für eine Aufnahme-URL; sonst 422 SSRF_BLOCKED). Ein leerer Wert oder ein Wert nur aus Leerzeichen bedeutet: kein Webhook. Erfordert einen API-Schlüssel: Aus einer Dashboard-Sitzung gesendet, wird er mit 400 WEBHOOK_REQUIRES_API_KEY abgelehnt, weil Webhooks mit dem Webhook-Secret des Schlüssels signiert werden.",
                    "format": "uri"
                  },
                  "rule_commitment_salt": {
                    "type": "string",
                    "description": "Optional. Ihr eigenes 32-Byte-Salt für die Regel-Commitments (redaction_manifest.json rule_commitments), als 64 Hexadezimalziffern oder base64 / base64url; ein Salt aus einem einzigen wiederholten Byte wird abgelehnt (400). Senden Sie es, um das Salt schon vor dem Aufruf zu besitzen: Ohne Angabe erzeugt der Server eines und gibt es nur in der Antwort 201 zurück, sodass bei einer durch ein Timeout verlorenen Antwort (504 vom Gateway, während die Schwärzung dennoch abgeschlossen wurde) das Salt verloren ist, und eine Wiederholung mit Idempotency-Key enthält es nicht. ProofSnap bewahrt es nicht auf."
                  }
                },
                "description": "Mindestens eines von boxes, text_redactions oder metadata_fields."
              },
              "example": {
                "text_redactions": [
                  {
                    "match": "jane.doe@example.com",
                    "reason": "personal data"
                  }
                ],
                "reason_summary": "Personal data of a third party"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Geschwärztes Paket bereit. Eine idempotente Wiederholung einer früheren Schwärzung mit demselben Idempotency-Key liefert ebenfalls 201, mit `Idempotent-Replay: true`: den ursprünglichen Body mit dem aktuellen Status und einer neu vorsignierten package_url (weggelassen, wenn das Paket nicht vorsigniert werden kann; lesen Sie sie dann mit GET /captures/{capture_id}).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redaction_id": {
                      "type": "string",
                      "example": "red_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed"
                      ]
                    },
                    "redacted_from": {
                      "type": "string"
                    },
                    "redaction_count": {
                      "type": "integer",
                      "description": "Anzahl der angewendeten Schwärzungsaktionen."
                    },
                    "original_signature_verified": {
                      "type": "boolean",
                      "description": "Ob die Signatur des Originalmanifests vor der Schwärzung erfolgreich verifiziert wurde."
                    },
                    "leakage_warnings": {
                      "type": "integer",
                      "description": "Immer 0: Eine Schwärzung, bei der die Prüfung nach der Schwärzung einen verbliebenen Begriff findet, wird mit 422 REDACTION_LEAK_DETECTED abgelehnt, statt ausgestellt zu werden. Aus Kompatibilitätsgründen beibehalten."
                    },
                    "package_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "package_url_expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "verify_url": {
                      "type": "string",
                      "description": "Link zum öffentlichen ProofSnap-Verifier.",
                      "format": "uri"
                    },
                    "ignored_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Nullbasierte Indizes in die boxes der Anfrage für die Boxen, die vollständig außerhalb des Screenshots lagen und nichts übermalt haben. Ebenfalls festgehalten in redaction_manifest.json (screenshot_boxes)."
                    },
                    "clipped_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Nullbasierte Indizes in die boxes der Anfrage für die Boxen, die über den Rand des Screenshots hinausragten und dort abgeschnitten wurden. Ebenfalls festgehalten in redaction_manifest.json (screenshot_boxes)."
                    },
                    "screenshot_method": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "jpeg_dct_blocks",
                        "decode_repaint_jpeg",
                        null
                      ],
                      "description": "Wie die Boxen übermalt wurden: jpeg_dct_blocks (die komprimierten Blöcke des JPEG unter jeder Box durch schwarze Blöcke ersetzt, alle anderen Blöcke unverändert) oder decode_repaint_jpeg (der Screenshot dekodiert, übermalt und neu kodiert). null, wenn keine Box etwas übermalt hat. Ebenfalls festgehalten in redaction_manifest.json (screenshot_boxes.method)."
                    },
                    "painted_rects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "x": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "y": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "w": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "h": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "required": [
                          "x",
                          "y",
                          "w",
                          "h"
                        ]
                      },
                      "description": "Die tatsächlich geschwärzten Bereiche in Pixeln des Screenshots, einer pro angewendeter Box in der Reihenfolge der boxes der Anfrage ohne die ignorierten. Bei jpeg_dct_blocks ist jeder die auf ganze Blöcke erweiterte Box, enthält die Box also; bei decode_repaint_jpeg ist es die auf den Screenshot begrenzte und auf ganze 8 x 8-Blöcke der neuen Datei erweiterte Box, die nach der Neukodierung geschwärzt werden, damit die Kompression keine benachbarte Farbe in die Box tragen kann. Ebenfalls festgehalten in redaction_manifest.json (screenshot_boxes.painted_rects)."
                    },
                    "text_rule_rects": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Verschiedene Bereiche des Screenshots, die nur für die Text-Schwärzungen geschwärzt wurden, ohne die Bereiche, die eine Box enthalten (ein Bereich, den zwei Regeln treffen, zählt einmal): in den Textblöcken der text_layout.json der Quelle, deren Text eine Regel trifft, die geschätzten Zeilen mit dem Treffer oder der ganze Block, wenn die Schätzung unsicher ist. Aufgeführt in redaction_manifest.json (screenshot_boxes.from_text_rules)."
                    },
                    "text_occurrences_not_located": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Vorkommen der Text-Schwärzungen im Seitentext, die auf dem Screenshot nicht verortet werden konnten und dort nicht geschwärzt wurden (Text, den die text_layout.json der Quelle nicht aufführt, etwa Text in einem Bild; ein Block, der zu groß ist, um ihn für einen Begriff zu schwärzen; Bereiche jenseits des Limits einer Schwärzung; ein Screenshot, der nicht übermalt werden konnte). Vorkommen in einem Block, der vollständig außerhalb des Screenshots liegt (dort gibt es nichts zu schwärzen), werden hier nicht gezählt; redaction_manifest.json führt sie als occurrences_not_on_screenshot auf. Nie mehr als die Vorkommen im Seitentext. Prüfen Sie den Screenshot und fügen Sie Boxen hinzu. redaction_manifest.json (leakage_check.screenshot_text_advisory) enthält die Anzahlen pro Regel."
                    },
                    "text_variants_remaining": {
                      "type": "object",
                      "description": "Hinweis, nie ein Fehler: pro wörtlicher Textregel die in domtextcontent.txt und im Text von page.html dieses Pakets verbliebenen Vorkommen, die die Regel nicht getroffen hat, die aber treffen, wenn Groß- und Kleinschreibung sowie diakritische Zeichen ignoriert werden (zum Beispiel „boskovické“ für die schreibungsabhängige Regel „Boskovic“), und ob der Begriff in der Aufnahme-URL oder der finalen URL steht, die nie geschwärzt werden. `total` addiert pro Regel den größeren der beiden Zählwerte (beide Dateien enthalten denselben Seitentext). Nur Anzahlen, nie der Text. Regex-Regeln werden nicht gezählt. Ebenfalls in redaction_manifest.json (leakage_check.text_variants_advisory). Vorhanden, wenn die Anfrage Text-Schwärzungen enthält.",
                      "properties": {
                        "total": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "per_rule": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rule_index": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_domtextcontent": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_page_html_text": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_capture_url": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "rules_in_capture_url": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Vorhanden, wenn die Anfrage Text-Schwärzungen enthält. Nullbasierte Indizes der Textregeln, deren Text auch in der Aufnahme-URL oder der finalen URL steht. Diese URLs werden nie geändert: Der Text wurde überall sonst entfernt und bleibt dort sichtbar. Leer, wenn keine Regel sie trifft. Ebenfalls in redaction_manifest.json (leakage_check.capture_url, `remains_in_capture_url: true` pro Regel) und im Schwärzungsbericht."
                    },
                    "rule_commitment_salt": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "Vorhanden, wenn die Anfrage Text-Schwärzungen enthält. Das 32-Byte-Salt (hexadezimal; das rule_commitment_salt der Anfrage, sofern eines gesendet wurde) der rule_commitments in redaction_manifest.json: pro Textregel der SHA-256 aus dem Salt der Regel, gefolgt von den UTF-8-Bytes des kanonischen JSON der Regel {\"match\": <text, or {\"pattern\", \"flags\"}>, \"case_insensitive\": <bool>}, wobei das Salt der Regel ein HMAC-SHA256 mit diesem Salt als Schlüssel über den UTF-8-Text \"rule:<rule_index>\" ist (so lässt sich eine Regel durch Offenlegung ihres eigenen Salts belegen, ohne die anderen). Die Regeln werden nicht gespeichert, und das Salt wird nur in dieser Antwort zurückgegeben: nicht in einer Wiederholung mit Idempotency-Key, einem Webhook, dem Ereignisprotokoll oder GET /captures/{capture_id}, und ProofSnap bewahrt es nicht auf. Bewahren Sie es bei Ihren Unterlagen auf: Damit können Sie belegen, welche Regeln die Schwärzung angewendet hat."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason; zum Beispiel eine Begründung, die kein reiner Text ist, oder das Regex-Flag y), REDACTION_NO_OPS (nichts angegeben oder nichts getroffen) oder INVALID_URL (webhook_url nicht https). WEBHOOK_REQUIRES_API_KEY: webhook_url aus einer Dashboard-Sitzung gesendet (Webhooks erfordern einen API-Schlüssel).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED oder PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: nicht Ihre Aufnahme. EIDAS_REQUIRES_LIVE_KEY: include_eidas mit einem Testschlüssel oder für eine Schwärzung einer Testaufnahme (dann mit details.reason). EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist. PLAN_ARCHIVE_ONLY: Der Tarif bewahrt nur das Archiv auf und erstellt keine neuen Vorgänge. PLAN_API_READ_ONLY: ein API-Schlüssel eines Kontos im Tarif Website Archive (dessen Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Kontos im Tarif Website Archive, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge in diesem Tarif werden nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "REDACTION_NOT_READY: Die Quellaufnahme ist nicht abgeschlossen. IDEMPOTENCY_IN_PROGRESS: Eine Anfrage mit demselben Idempotency-Key läuft noch (details.capture_id ist die Schwärzungs-ID, die mit GET /captures/{capture_id} abgefragt werden kann). COMPANY_READ_ONLY: Die Quelle gehört einem Unternehmen, das als Firmenarchiv geführt wird.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "REDACTION_SCREENSHOT_UNREADABLE: Die Bilddaten des Screenshots sind unlesbar, oder der Screenshot liegt über dem Limit für Box-Schwärzungen (150 Megapixel für ein JPEG in der Form, in der die Aufnahme es schreibt, 60 Megapixel für ein PNG oder ein anderes JPEG); Text- und Metadaten-Schwärzungen funktionieren auch ohne Boxen. REDACTION_LEAK_DETECTED: Ein geschwärzter Begriff ist außerhalb der Aufnahme-URL erhalten geblieben (details { files, rule_indexes, metadata_fields, in_capture_url }; in_capture_url ist true, wenn der Text der abgelehnten Regel auch in der Aufnahme-URL oder der finalen URL steht, wo er nie gezählt wird). REDACTION_SOURCE_UNSUPPORTED: Die Quelle ist keine Aufnahme einer Webseite (details { source } oder { manifest_type }). SSRF_BLOCKED: webhook_url zielt auf eine gesperrte Adresse. REDACTION_SOURCE_INTEGRITY_FAILED: Eine Datei des Quellpakets stimmt nicht mit dem in seinem signierten Manifest aufgeführten SHA-256 überein, oder eine aufgeführte Datei fehlt (details { mismatched, missing }, nur Dateinamen); es wurde nichts geschwärzt und nichts berechnet. IDEMPOTENCY_KEY_REUSED: Der Idempotency-Key wurde bereits mit einem anderen Request-Body (oder aus einer anderen Schlüsselumgebung) verwendet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zu viele Anfragen im aktuellen 60-Sekunden-Fenster. `error.details.retry_after_seconds` wiederholt Retry-After. RateLimit meldet r=0 und die Sekunden bis zum Zurücksetzen des Fensters. Ebenfalls RATE_LIMITED, für eine Dashboard-Sitzung im Tarif Website Archive: deren Tageslimit von 100 Einheiten pro UTC-Tag, gemeinsam für Einzel- und Batch-Aufnahmen (eine Einheit pro URL), Vergleich, Schwärzung und certify-hash (eine Einheit pro Anfrage); eine Anfrage, die nicht hineinpasst, wird vollständig abgelehnt (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After und details.retry_after_seconds geben die Sekunden bis zum nächsten UTC-Tag an, und die minütlichen Header RateLimit und X-RateLimit-* entfallen). Eine kurze Ablehnung nur mit details.retry_after_seconds und Retry-After: 1 bedeutet eine gleichzeitige Aktualisierung des Zählers: Wiederholen Sie die Anfrage. Eine Ablehnung, die nicht vom Aufrufer verschuldet ist, gibt die Einheiten zurück; mit 202 angenommene Vorgänge, die später fehlschlagen, behalten ihre Einheit. Eine weiche Sperre, keine Sicherheitsgrenze.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: unerwarteter Serverfehler; wiederholen Sie die Anfrage mit exponentiellem Backoff. CAPTURE_TIME_BUDGET_EXCEEDED: Die synchrone Schwärzung hat ihr Zeitbudget überschritten und wurde gestoppt, bevor ein Paket ausgestellt wurde; es wurde nichts berechnet. Mit `details.reason` `source_read` hat das Lesen des Quellpakets den Großteil des Budgets verbraucht: Das Paket ist derzeit zu groß zum Schwärzen, versuchen Sie es später erneut.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/compare": {
      "post": {
        "tags": [
          "Vergleichen und Schwärzen"
        ],
        "operationId": "compareCaptures",
        "summary": "Zwei Aufnahmen vergleichen",
        "description": "Vergleicht zwei Ihrer abgeschlossenen Aufnahmen (Seitentext, die auf der Seite angezeigten Bilder, Metadaten) und erstellt ein signiertes Vergleichspaket. Liefert 202; fragen Sie GET /captures/{compare_id} ab, das dann type, compared und diff_summary enthält. Die Bilder werden anhand des Bildinventars verglichen, das jede Quellaufnahme versiegelt hat (image_inventory.json: die Adresse jedes angezeigten Bildes und der SHA-256 seiner Bytes), nie Pixel für Pixel; die Ergebnisse stehen in image_changes.json und in diff_summary.image_changes. Eine Quelle, die aufgenommen wurde, bevor Aufnahmen ein Bildinventar festhielten, wird trotzdem verglichen, jedoch ohne Bildvergleich (diff_summary.image_changes.status not_run). Wird als eine Aufnahme abgerechnet. Nur Gleichartiges wird verglichen (seit 2026-10-14): Zwei Aufnahmen, die mit einem anderen Geräteprofil oder mit anderen Aufnahmeeinstellungen gerendert wurden, werden mit 422 COMPARE_PROFILE_MISMATCH abgelehnt (details.differing, details.before, details.after: nur die abweichenden Felder, injiziertes CSS und JavaScript als deren SHA-256), sofern allow_profile_mismatch nicht true ist, und zwei Aufnahmen verschiedener Seiten mit 422 COMPARE_URL_MISMATCH (details.before und details.after: url und final_url als Origin und Pfad), sofern allow_url_mismatch nicht true ist. Die Prüfung läuft zuerst auf den beiden Aufnahmeeinträgen (bevor etwas gelesen oder reserviert wird), dann auf den versiegelten metadata.json und forensic_log.json beider Pakete im Rahmen der unten beschriebenen Quellprüfung; eine Abweichung, die erst der Vergleichslauf findet, lässt den Vergleich mit diesem Code fehlschlagen und gibt die monatliche Einheit zurück. Der Text wird Zeile für Zeile mit einem deterministischen Zeilen-Diff verglichen (ohne Timeout): diff_summary zählt Zeilen (text_unit line). Nur Web-Aufnahmen (über die API oder durch einen Zeitplan erstellt) können verglichen werden: Eine Hash-Zertifizierung, ein Vergleich oder ein geschwärztes Paket wird mit 422 COMPARE_SOURCE_UNSUPPORTED (`details.capture_id`, `details.source`) abgelehnt, bevor ein Paket gelesen und bevor etwas reserviert wird. Bevor etwas verglichen oder reserviert wird, wird der SHA-256 jeder Datei neu berechnet, die in der manifest.json eines Quellpakets mit einem SHA-256 aufgeführt ist; eine Quelle mit einer veränderten oder fehlenden Datei oder ein Manifest, das keine Datei mit einem SHA-256 aufführt, wird mit 422 COMPARE_SOURCE_INTEGRITY_FAILED abgelehnt (kein Vergleich, kein Eintrag, keine monatliche Einheit). Nur diese aufgeführten Dateien werden verglichen: Eine Datei in einem Quellpaket, die dessen Manifest nicht mit einem SHA-256 aufführt (zum Beispiel eine hinzugefügte text_layout.json, image_inventory.json, page.html oder domtextcontent.txt), gilt als nicht vorhanden. Die Prüfung läuft bei der Anfrage, wenn beide Quellpakete 50 MB oder kleiner sind und sie in etwa 10 Sekunden abgeschlossen ist; andernfalls wird die Anfrage angenommen und der Vergleichslauf wiederholt die Prüfung, und eine fehlerhafte Quelle lässt dann den Vergleich fehlschlagen (GET /captures/{compare_id} zeigt status failed mit error.code COMPARE_SOURCE_INTEGRITY_FAILED) und gibt die monatliche Einheit zurück. Ein abgeschlossener Vergleich hält diese Prüfung in compare.json `sources[].source_files_reverified` und in comparison_method.json `source_verification` fest. /compare sendet keine Webhooks: Eine webhook_url im Body wird ignoriert. Fragen Sie GET /captures/{compare_id} ab.\n\nEin Vergleich, an dem eine Testaufnahme beteiligt ist (erstellt mit einem Testschlüssel, in der Sandbox oder von einem Konto ohne verknüpftes Abrechnungskonto), ist ein Testpaket, unabhängig davon, welcher Schlüssel ihn anfordert: mit Wasserzeichen, `test_mode: true` in manifest.json, `test_mode: true` für diese Quelle in compare.json `sources[]` und ein Testeintrag (key_env test), der nicht abgerechnet wird und keine monatliche Einheit verbraucht. include_eidas mit einer Testquelle wird mit 403 EIDAS_REQUIRES_LIVE_KEY (details.reason) abgelehnt.\n\nRate-Limit pro Konto in 60-Sekunden-Fenstern (alle API-Schlüssel des Kontos und seine Dashboard-Sitzung teilen sich ein Fenster). Sobald der Limiter gelaufen ist, enthalten Antworten dieses Endpunkts die Header `RateLimit-Policy` und `RateLimit` (basierend auf IETF draft-ietf-httpapi-ratelimit-headers) sowie die älteren `X-RateLimit-Limit`, `X-RateLimit-Remaining` und `X-RateLimit-Reset`. Eine 429 ergänzt `Retry-After`.\n\nSenden Sie einen Idempotency-Key, damit eine Wiederholung (etwa nach einem 504) die erste Antwort zurückgibt statt eines zweiten, abgerechneten Laufs (24 Stunden, dieselben Regeln wie bei POST /captures). Der Key wird vor der Quellprüfung beansprucht, sodass eine Wiederholung, während die erste Anfrage noch läuft, 409 IDEMPOTENCY_IN_PROGRESS liefert; eine Anfrage, die von der Quellprüfung oder vom Kontingent abgelehnt wird, gibt ihren Key frei, sodass eine Wiederholung mit demselben Key erneut geprüft wird.\n\nEin importiertes Beweispaket (source import, IDs mit dem Präfix `imp_`) kann nicht verglichen werden: 422 IMPORT_NOT_SUPPORTED (details.capture_id).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_id_1": {
                    "type": "string",
                    "description": "Die frühere Aufnahme."
                  },
                  "capture_id_2": {
                    "type": "string",
                    "description": "Die spätere Aufnahme. Muss sich von capture_id_1 unterscheiden."
                  },
                  "language": {
                    "type": "string",
                    "description": "Sprache des Vergleichs-PDF. Nur der exakte Code; jeder andere Wert fällt stillschweigend auf Englisch zurück.",
                    "enum": [
                      "en",
                      "de",
                      "it",
                      "fr",
                      "es",
                      "pt_BR",
                      "cs",
                      "el",
                      "hi",
                      "hu",
                      "id",
                      "ja",
                      "ko",
                      "nl",
                      "pl",
                      "ro",
                      "ru",
                      "sv",
                      "tr",
                      "vi",
                      "zh_CN",
                      "zh_TW",
                      "ar",
                      "he",
                      "da",
                      "fi",
                      "nb",
                      "sk",
                      "uk"
                    ]
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Wie bei CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Eine unsignierte Zertifizierungsvorlage (rule_902_certification.pdf, siehe CaptureOptions.include_rule_902) für den Vergleich hinzufügen. Nicht verfügbar für unterschiedlich gerenderte Aufnahmen: Mit allow_profile_mismatch true und einer Abweichung bei Profil oder Einstellungen 422 RULE902_NOT_AVAILABLE_FOR_MISMATCH. Derzeit deaktiviert: Die Zertifizierungsvorlage ist vorübergehend abgeschaltet, daher wird dieses Feld akzeptiert und ignoriert (es wird keine rule_902_certification.pdf erzeugt oder im Paket aufgeführt), und die Antwort enthält die Warnung RULE902_DISABLED.",
                    "default": false
                  },
                  "allow_profile_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Zwei Aufnahmen vergleichen, obwohl sie mit einem anderen Geräteprofil (options.viewport mobile oder desktop, viewport_width, viewport_height, device_scale_factor; versiegelt: Viewport-Größe, Geräte-Skalierungsfaktor, mobile Emulation, User-Agent ohne Versionsnummern) oder mit anderen Aufnahmeeinstellungen erstellt wurden, die das Rendering einer Seite verändern (dark_mode, block_ads, block_resources, inject_css, inject_js per SHA-256, expand_collapsed, dismiss_consent, dismiss_popups, ganze Seite). Ohne diese Option wird ein solches Paar mit 422 COMPARE_PROFILE_MISMATCH abgelehnt. Mit ihr sind diff_summary.text_verdict und images_verdict undetermined, changed ist null, sofern sich kein inhaltliches Metadatenfeld unterscheidet, und include_rule_902 wird abgelehnt."
                  },
                  "allow_url_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Zwei Aufnahmen verschiedener Seiten vergleichen. Zwei Aufnahmen gelten als dieselbe Seite, wenn eine Adresse der einen (die angeforderte url, die url und die final_url, die ihre metadata.json festhält) nach der Normalisierung einer Adresse der anderen entspricht (Host in Kleinbuchstaben, http und https gleich, Standardport, Fragment, ein abschließender Schrägstrich und die flüchtigen Query-Parameter weggelassen). Ohne diese Option werden verschiedene Seiten mit 422 COMPARE_URL_MISMATCH abgelehnt."
                  }
                },
                "required": [
                  "capture_id_1",
                  "capture_id_2"
                ]
              },
              "example": {
                "capture_id_1": "cap_a1B2c3D4e5F6g7H8",
                "capture_id_2": "cap_h8G7f6E5d4C3b2A1"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Angenommen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "compare_id": {
                      "type": "string",
                      "example": "cmp_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: ungültiges JSON, eine fehlende ID, dieselbe ID zweimal oder allow_profile_mismatch / allow_url_mismatch, das kein Boolean ist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED oder PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: Eine Aufnahme gehört nicht Ihnen. EIDAS_REQUIRES_LIVE_KEY: include_eidas mit einem Testschlüssel oder mit einer Test-Quellaufnahme (dann mit details.reason). EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist. PLAN_ARCHIVE_ONLY: Der Tarif bewahrt nur das Archiv auf und erstellt keine neuen Vorgänge. PLAN_API_READ_ONLY: ein API-Schlüssel eines Kontos im Tarif Website Archive (dessen Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Kontos im Tarif Website Archive, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge in diesem Tarif werden nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "COMPARE_NOT_READY: Beide Aufnahmen müssen abgeschlossen sein. COMPANY_READ_ONLY: Eine Quelle gehört einem Unternehmen, das als Firmenarchiv geführt wird (daraus wird nichts Neues erstellt).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "COMPARE_SOURCE_INTEGRITY_FAILED: Eine Datei einer Quellaufnahme stimmt nicht mit dem in ihrem signierten Manifest aufgeführten SHA-256 überein oder fehlt; es wurde nichts verglichen und nichts berechnet. `details.capture_id`, `details.mismatched`, `details.missing` benennen die erste fehlerhafte Quelle; `details.reason` ist vorhanden, wenn das Quellmanifest selbst nicht verwendet werden kann (zum Beispiel \"the source manifest lists no file with a SHA-256\" oder \"the source manifest lists no files\"); `details.sources[]` führt jede fehlerhafte Quelle mit denselben Feldern auf. Prüfen Sie die Quellaufnahme (POST /captures/{capture_id}/verify). COMPARE_SOURCE_UNSUPPORTED: Eine Quelle ist keine Web-Aufnahme (eine Hash-Zertifizierung, ein Vergleich oder ein geschwärztes Paket); es wurde nichts gelesen, verglichen oder berechnet. `details.capture_id` benennt diese Quelle und `details.source` ihre Art, sofern der Eintrag eine festhält. Vergleichen Sie stattdessen die ursprünglichen Web-Aufnahmen. COMPARE_PROFILE_MISMATCH: Die beiden Aufnahmen wurden mit einem anderen Geräteprofil oder anderen Aufnahmeeinstellungen erstellt; `details.differing` führt die Feldnamen auf (Eintragsebene: viewport, viewport_width, viewport_height, device_scale_factor, dark_mode, block_ads, block_resources, inject_css, inject_js, expand_collapsed, dismiss_consent, dismiss_popups, full_page; versiegelte Ebene: viewport_width, viewport_height, device_scale_factor, is_mobile, user_agent und dieselben Einstellungen), `details.before` und `details.after` deren Werte. Senden Sie allow_profile_mismatch: true, um trotzdem zu vergleichen. COMPARE_URL_MISMATCH: Die beiden Aufnahmen stammen von verschiedenen Seiten; `details.before` und `details.after` geben url und final_url als Origin und Pfad an. Senden Sie allow_url_mismatch: true, um trotzdem zu vergleichen. RULE902_NOT_AVAILABLE_FOR_MISMATCH: include_rule_902 mit allow_profile_mismatch für unterschiedlich gerenderte Aufnahmen; `details.differing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zu viele Anfragen im aktuellen 60-Sekunden-Fenster. `error.details.retry_after_seconds` wiederholt Retry-After. RateLimit meldet r=0 und die Sekunden bis zum Zurücksetzen des Fensters. Ebenfalls RATE_LIMITED, für eine Dashboard-Sitzung im Tarif Website Archive: deren Tageslimit von 100 Einheiten pro UTC-Tag, gemeinsam für Einzel- und Batch-Aufnahmen (eine Einheit pro URL), Vergleich, Schwärzung und certify-hash (eine Einheit pro Anfrage); eine Anfrage, die nicht hineinpasst, wird vollständig abgelehnt (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After und details.retry_after_seconds geben die Sekunden bis zum nächsten UTC-Tag an, und die minütlichen Header RateLimit und X-RateLimit-* entfallen). Eine kurze Ablehnung nur mit details.retry_after_seconds und Retry-After: 1 bedeutet eine gleichzeitige Aktualisierung des Zählers: Wiederholen Sie die Anfrage. Eine Ablehnung, die nicht vom Aufrufer verschuldet ist, gibt die Einheiten zurück; mit 202 angenommene Vorgänge, die später fehlschlagen, behalten ihre Einheit. Eine weiche Sperre, keine Sicherheitsgrenze.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/certify-hash": {
      "post": {
        "tags": [
          "Hash-Zertifizierung"
        ],
        "operationId": "certifyHash",
        "summary": "Datei-Hashes zertifizieren",
        "description": "Senden Sie einen SHA-256 (hash) oder bis zu 100 (hashes). Das Paket enthält manifest.json (mit Ihren Hashes), manifest.sig, publickey.pem, evidence.pdf, die Skripte unter verification/ und, sofern verfügbar, manifest.json.ots.\n\n- Ein Hash: synchron, liefert 200 mit dem fertigen Zertifikat.\n- Mehrere Hashes mit combined: true: ein Zertifikat, das jeden Hash aufführt, synchron, liefert 200.\n- Mehrere Hashes ohne combined: ein unabhängiges Zertifikat pro Hash, im Hintergrund gestartet; liefert 202 mit den certify_ids. Fragen Sie GET /captures/{certify_id} ab; mit webhook_url sendet jedes certify.completed.\n\nJedes Zertifikat zählt als eine Aufnahme für die Abrechnung, das monatliche Limit und das Rate-Limit (N Hashes ohne combined zählen N; ist N größer als das Limit pro Fenster, folgt 400 RATE_LIMIT_COST_TOO_HIGH). Ein `label` wird auf das Zertifikat gedruckt: Es wird auf 200 Zeichen gekürzt und muss reiner Text sein (keine Steuerzeichen, unsichtbaren Zeichen oder Textrichtungszeichen, sonst 400). In einem Batch benennt ein Fehler zu einem Element dieses in `error.details.index`.\n\nRate-Limit pro Konto in 60-Sekunden-Fenstern (alle API-Schlüssel des Kontos und seine Dashboard-Sitzung teilen sich ein Fenster). Sobald der Limiter gelaufen ist, enthalten Antworten dieses Endpunkts die Header `RateLimit-Policy` und `RateLimit` (basierend auf IETF draft-ietf-httpapi-ratelimit-headers) sowie die älteren `X-RateLimit-Limit`, `X-RateLimit-Remaining` und `X-RateLimit-Reset`. Eine 429 ergänzt `Retry-After`.\n\nSenden Sie einen Idempotency-Key, damit eine Wiederholung (etwa nach einem 504) die erste Antwort zurückgibt statt eines zweiten, abgerechneten Laufs (24 Stunden, dieselben Regeln wie bei POST /captures).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 als 64 Hexadezimalzeichen (Groß- und Kleinschreibung egal). Verwenden Sie hash oder hashes."
                  },
                  "label": {
                    "type": "string",
                    "description": "Für die Datei angezeigter Name. Längere Werte werden auf 200 Zeichen gekürzt."
                  },
                  "hashes": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "SHA-256 hexadezimal."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "hash": {
                              "type": "string"
                            },
                            "label": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "hash"
                          ]
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "combined": {
                    "type": "boolean",
                    "description": "Alle Hashes in einem Zertifikat zertifizieren.",
                    "default": false
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "sha256"
                    ],
                    "default": "sha256"
                  },
                  "language": {
                    "type": "string",
                    "description": "Sprache der menschenlesbaren PDFs. Einer der Werte: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Groß- und Kleinschreibung, Bindestrich und Region werden toleriert („DE“, „fr-FR“ und „PT-BR“ werden akzeptiert); ein bloßes „pt“ oder „zh“ wird abgelehnt. Ein nicht unterstützter Wert liefert 400 MALFORMED_REQUEST."
                  },
                  "case_reference": {
                    "type": "string",
                    "description": "Wird auf das Zertifikat gedruckt. Folgt der Regel für reinen Text (siehe PlainText); höchstens 200 Zeichen.",
                    "maxLength": 200
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Wie bei CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Eine unsignierte Zertifizierungsvorlage hinzufügen (rule_902_certification.pdf, siehe CaptureOptions.include_rule_902). Derzeit deaktiviert: Die Zertifizierungsvorlage ist vorübergehend abgeschaltet, daher wird dieses Feld akzeptiert und ignoriert (es wird keine rule_902_certification.pdf erzeugt oder im Paket aufgeführt), und die Antwort enthält die Warnung RULE902_DISABLED.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "https-URL, die certify.completed empfängt. Die URL muss https verwenden und darf nicht auf eine private, Loopback-, Link-Local-, Carrier-Grade-NAT- oder Cloud-Metadaten-Adresse zeigen (dieselben Regeln wie für eine Aufnahme-URL; sonst 422 SSRF_BLOCKED). Ein leerer Wert oder ein Wert nur aus Leerzeichen bedeutet: kein Webhook. Erfordert einen API-Schlüssel: Aus einer Dashboard-Sitzung gesendet, wird er mit 400 WEBHOOK_REQUIRES_API_KEY abgelehnt, weil Webhooks mit dem Webhook-Secret des Schlüssels signiert werden.",
                    "format": "uri"
                  }
                }
              },
              "example": {
                "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "label": "contract.pdf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Zertifikat bereit (ein Hash oder kombiniert).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CertifyResult"
                }
              }
            }
          },
          "202": {
            "description": "Mehrere unabhängige Zertifikate gestartet. Ein Zertifikat, das das monatliche Limit oder das Sandbox-Kontingent bei seiner Ausführung ablehnt, schlägt fehl: Sein Eintrag (GET /captures/{certify_id}) hat status failed und error.code QUOTA_EXCEEDED.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch_id": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "certify_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "IDs der erstellten Zertifikate (einschließlich eines, dessen Start fehlgeschlagen ist)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "certificates": {
                      "type": "array",
                      "description": "Ein Eintrag pro übermitteltem Hash, in Reihenfolge. status failed: Dieses Zertifikat konnte nicht gestartet werden (sein Eintrag, sofern einer erstellt wurde, ist als fehlgeschlagen markiert); die anderen laufen weiter.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "certify_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "hash_sha256": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "processing",
                              "failed"
                            ]
                          }
                        }
                      }
                    },
                    "failed_count": {
                      "type": "integer",
                      "description": "Wie viele Einträge von certificates nicht gestartet werden konnten. Wenn alle fehlgeschlagen sind, lautet die Antwort stattdessen 500 CONFIGURATION_ERROR."
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason benennt das Feld, zum Beispiel \"label must be a string\"; details.index benennt das Element eines Batches), INVALID_HASH (details.index in einem Batch) oder INVALID_URL (webhook_url nicht https oder mit Anmeldedaten). WEBHOOK_REQUIRES_API_KEY: webhook_url aus einer Dashboard-Sitzung gesendet (Webhooks erfordern einen API-Schlüssel). RATE_LIMIT_COST_TOO_HIGH: mehr Hashes (ohne combined), als ein Rate-Limit-Fenster erlaubt; details { max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED oder, auf den synchronen Pfaden, QUOTA_EXCEEDED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY (details.reason \"no_billing_account\", wenn ein Live-API-Schlüssel oder eine Sitzung als Test läuft, weil kein Abrechnungskonto verknüpft ist). EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos. PLAN_ARCHIVE_ONLY: Der Tarif bewahrt nur das Archiv auf und erstellt keine neuen Vorgänge. PLAN_API_READ_ONLY: ein API-Schlüssel eines Kontos im Tarif Website Archive (dessen Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Kontos im Tarif Website Archive, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge in diesem Tarif werden nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: webhook_url zielt auf eine private, Loopback-, Link-Local- oder anderweitig gesperrte Adresse.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zu viele Anfragen im aktuellen 60-Sekunden-Fenster. `error.details.retry_after_seconds` wiederholt Retry-After. RateLimit meldet r=0 und die Sekunden bis zum Zurücksetzen des Fensters. Ebenfalls RATE_LIMITED, für eine Dashboard-Sitzung im Tarif Website Archive: deren Tageslimit von 100 Einheiten pro UTC-Tag, gemeinsam für Einzel- und Batch-Aufnahmen (eine Einheit pro URL), Vergleich, Schwärzung und certify-hash (eine Einheit pro Anfrage); eine Anfrage, die nicht hineinpasst, wird vollständig abgelehnt (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After und details.retry_after_seconds geben die Sekunden bis zum nächsten UTC-Tag an, und die minütlichen Header RateLimit und X-RateLimit-* entfallen). Eine kurze Ablehnung nur mit details.retry_after_seconds und Retry-After: 1 bedeutet eine gleichzeitige Aktualisierung des Zählers: Wiederholen Sie die Anfrage. Eine Ablehnung, die nicht vom Aufrufer verschuldet ist, gibt die Einheiten zurück; mit 202 angenommene Vorgänge, die später fehlschlagen, behalten ihre Einheit. Eine weiche Sperre, keine Sicherheitsgrenze.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: Die Zertifizierung ist fehlgeschlagen (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: Die synchrone Zertifizierung (ein Hash oder kombiniert) wurde nicht innerhalb von etwa 25 s abgeschlossen. Sie kann trotzdem noch abgeschlossen werden: details.certify_id und details.poll_path benennen den Eintrag, der mit GET /captures/{capture_id} abgefragt werden kann.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/webhook-events/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookEvents",
        "summary": "Webhook-Zustellprotokoll",
        "description": "Jeder Zustellversuch für eine Aufnahme, ein Zertifikat oder eine Schwärzung, die ältesten zuerst.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Zustellprotokoll.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "webhook_url": {
                      "type": "string",
                      "description": "Die Webhook-URL des Eintrags (options.webhook_url einer Aufnahme oder die mit einem Zertifikat oder einer Schwärzung angegebene URL; null, wenn keine vorhanden ist). Für den Inhaber des Unternehmens, für das eine Aufnahme erstellt wurde, beim Lesen des Protokolls eines Platzinhabers nur Schema, Host, Port und Pfad (Query und Fragment entfernt, da sie ein Token enthalten können); dieser Lesezugriff wird im Zugriffsverlauf der Aufnahme festgehalten (action webhook_events).",
                      "nullable": true
                    },
                    "attempts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "attempt_number": {
                            "type": "integer"
                          },
                          "attempted_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "status_code": {
                            "type": "integer",
                            "nullable": true
                          },
                          "latency_ms": {
                            "type": "integer",
                            "nullable": true
                          },
                          "success": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": "string",
                            "nullable": true,
                            "description": "null, wenn zugestellt. Andernfalls eine allgemeine Klasse, nie der rohe Netzwerkfehler: connection_failed, timeout, blocked_address (der Host wurde zum Zustellzeitpunkt zu einer privaten oder anderweitig gesperrten Adresse aufgelöst), redirect_not_followed (der Endpunkt antwortete mit 3xx; Weiterleitungen werden nie verfolgt), http_<status> (eine Antwort außerhalb von 2xx) oder ein Satz, der angibt, warum das Ereignis nicht gesendet wurde (kein API-Schlüssel, Schlüssel widerrufen oder nicht gefunden, kein Webhook-Secret, Abruf des Secrets fehlgeschlagen)."
                          },
                          "delivery_id": {
                            "type": "string",
                            "description": "Der Header webhook-id dieser Zustellung ohne das Präfix `msg_`."
                          }
                        }
                      }
                    },
                    "attempt_count": {
                      "type": "integer"
                    },
                    "last_success": {
                      "type": "string",
                      "description": "attempted_at des letzten erfolgreichen Versuchs.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: nicht Ihre Aufnahme.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures": {
      "post": {
        "tags": [
          "Automatische Aufnahmen"
        ],
        "operationId": "createSchedule",
        "summary": "Zeitplan erstellen",
        "description": "interval und cron: Jeder Lauf erstellt eine Aufnahme und sendet mit webhook_url capture.completed oder capture.failed.\n\non_change: Jeder Lauf ruft zuerst die Seite ab (reines HTTP oder ein Browser-Rendering mit js_render) und hasht ihren normalisierten Text. Diese Prüfung ist keine Aufnahme und wird nicht berechnet. Der erste Lauf erstellt eine Basisaufnahme. Ändert sich der Hash, wird eine neue Aufnahme erstellt, mit der vorherigen verglichen und monitor.change_detected an webhook_url gesendet. Eine Seite, die einen Fehler oder fast keinen Text liefert, gilt als keine Änderung.\n\nNach 5 aufeinanderfolgenden fehlgeschlagenen Läufen pausiert ein Zeitplan sich selbst. Ist das monatliche Limit erreicht oder das Abonnement überfällig, werden Läufe übersprungen und der Zeitplan bleibt aktiv.\n\nLimits pro Tarif:\n\n| Tarif | Aktive Zeitpläne | Minimales Intervall | on_change |\n| --- | --- | --- | --- |\n| sandbox | 0 | nicht verfügbar | nein |\n| developer | 3 | 300 s | ja |\n| startup | 25 | 300 s | ja |\n| growth | 200 | 300 s | ja |\n| custom | unbegrenzt | 300 s | ja |\n| legal_solo | 3 | 300 s | ja |\n| legal_seats | 3 | 300 s | ja |\n| website_archive | 30 | 86.400 s | ja |\n| archive_only | 0 | nicht verfügbar | nein |\n\nIdempotency-Key: Eine Wiederholung mit demselben Key und Body innerhalb von 24 Stunden erhält die erste Antwort 201 zurück (Header Idempotent-Replay: true) statt eines zweiten Zeitplans; derselbe Key mit einem anderen Body ergibt 422 IDEMPOTENCY_KEY_REUSED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Erstellt.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST, INVALID_URL, INVALID_CRON, INTERVAL_TOO_SHORT (details.min_interval_seconds) oder ein Fehler in den Aufnahmeoptionen. WEBHOOK_REQUIRES_API_KEY: webhook_url aus einer Dashboard-Sitzung gesendet (Webhooks erfordern einen API-Schlüssel).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED (details.max_active, details.active) oder ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist. PLAN_ARCHIVE_ONLY: Der Tarif bewahrt nur das Archiv auf und erstellt keine neuen Vorgänge. PLAN_API_READ_ONLY: ein API-Schlüssel eines Kontos im Tarif Website Archive (dessen Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Kontos im Tarif Website Archive, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge in diesem Tarif werden nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url oder webhook_url zielt auf eine private, Loopback-, Link-Local- oder anderweitig gesperrte Adresse.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      },
      "get": {
        "tags": [
          "Automatische Aufnahmen"
        ],
        "operationId": "listSchedules",
        "summary": "Zeitpläne auflisten",
        "description": "Bis zu 100 Ihrer Zeitpläne.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Zeitpläne.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedules": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "Automatische Aufnahmen"
        ],
        "operationId": "getSchedule",
        "summary": "Zeitplan abrufen",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Der Zeitplan.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Automatische Aufnahmen"
        ],
        "operationId": "updateSchedule",
        "summary": "Zeitplan pausieren, fortsetzen oder bearbeiten",
        "description": "Senden Sie status, um zu pausieren oder fortzusetzen, und/oder ein beliebiges Feld von ScheduleRequest, um zu bearbeiten. Ein gesendetes Feld der obersten Ebene ersetzt das gespeicherte, auch ein explizites null (webhook_url, timezone und js_render werden auf diese Weise geleert); ein weggelassenes Feld behält seinen gespeicherten Wert; das Ergebnis wird anschließend als Ganzes erneut validiert. `options` ist die Ausnahme von der feldweisen Bearbeitung: Es wird als ganze Map ersetzt, nicht Schlüssel für Schlüssel zusammengeführt, senden Sie also die vollständigen gewünschten Optionen (lesen Sie sie vorher mit GET). Eine Option, die Sie in der Map weglassen, wird entfernt. Das Fortsetzen prüft das Limit aktiver Zeitpläne, setzt den Fehlerzähler zurück und berechnet den nächsten Lauf ab jetzt.\n\nAus einer Dashboard-Sitzung kann keine webhook_url gesetzt werden (400 WEBHOOK_REQUIRES_API_KEY); null oder ein leerer String leert sie. Ein aus einer Dashboard-Sitzung erstellter Zeitplan hatte nie einen API-Schlüssel zum Signieren, daher entfernt eine Bearbeitung auch jede früher darin gespeicherte webhook_url, und auch ein API-Schlüssel kann keine hinzufügen (400 WEBHOOK_REQUIRES_API_KEY): Der Scheduler signiert die Webhooks eines Zeitplans mit dem Webhook-Secret des Schlüssels, mit dem der Zeitplan erstellt wurde. Um Webhooks zu erhalten, erstellen Sie den Zeitplan mit einem API-Schlüssel.\n\nEine Änderung von url, mode oder js_render eines on_change-Zeitplans (oder ein Wechsel eines Zeitplans zu oder von on_change) löscht seine Basis (den gespeicherten Prüf-Hash und die letzte Aufnahme), sodass der nächste Lauf eine neue Basisaufnahme erstellt, statt das neue Ziel mit dem alten zu vergleichen. Der Request-Body muss ein JSON-Objekt sein (sonst 400 MALFORMED_REQUEST).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "interval",
                      "cron",
                      "on_change"
                    ]
                  },
                  "interval_seconds": {
                    "type": "integer"
                  },
                  "cron_expr": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string",
                    "nullable": true,
                    "description": "IANA-Zeitzone für cron. null oder ein leerer String setzt auf UTC zurück."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Senden Sie null oder einen leeren String, um sie zu entfernen. Die URL muss https verwenden und darf nicht auf eine private, Loopback-, Link-Local-, Carrier-Grade-NAT- oder Cloud-Metadaten-Adresse zeigen (dieselben Regeln wie für eine Aufnahme-URL; sonst 422 SSRF_BLOCKED). Erfordert auf beiden Seiten einen API-Schlüssel: Aus einer Dashboard-Sitzung gesendet oder mit einem API-Schlüssel für einen im Dashboard erstellten Zeitplan gesendet, wird sie mit 400 WEBHOOK_REQUIRES_API_KEY abgelehnt, weil Webhooks mit dem Webhook-Secret des Schlüssels signiert werden, mit dem der Zeitplan erstellt wurde.",
                    "nullable": true
                  },
                  "js_render": {
                    "type": "boolean",
                    "nullable": true,
                    "description": "null bedeutet false."
                  },
                  "options": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CaptureOptions"
                      }
                    ],
                    "description": "Ersetzt die gespeicherten Optionen als Ganzes (nicht Schlüssel für Schlüssel zusammengeführt). Senden Sie die vollständige Map; eine weggelassene Option wird entfernt. null setzt die Optionen auf ihre Standardwerte zurück. options.webhook_url wird nie gespeichert (siehe webhook_url)."
                  },
                  "max_capture_age_seconds": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 86400,
                    "maximum": 34560000,
                    "description": "Nur on_change: eine vollständige Aufnahme mindestens alle N Sekunden, auch ohne Änderung (1 bis 400 Tage, länger als interval_seconds). null entfernt sie. Ein Wechsel des Modus zu interval oder cron verwirft sie."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Der aktualisierte Zeitplan.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (auch wenn nichts zu aktualisieren ist), INVALID_URL, INVALID_CRON oder INTERVAL_TOO_SHORT. WEBHOOK_REQUIRES_API_KEY: webhook_url aus einer Dashboard-Sitzung gesendet oder mit einem API-Schlüssel für einen im Dashboard erstellten Zeitplan gesendet (er hat keinen Schlüssel, dessen Webhook-Secret die Webhooks signieren könnte).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED oder ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist (beim Fortsetzen eines pausierten Zeitplans; Pausieren und Bearbeiten bleiben möglich). PLAN_API_READ_ONLY: ein API-Schlüssel eines Kontos im Tarif Website Archive, bei jedem PATCH (einschließlich Pausieren und Bearbeiten). PLAN_API_READ_ONLY mit details.reason \"session_outside_dashboard\": eine Dashboard-Sitzung eines Kontos im Tarif Website Archive, deren Anfrage keinen Origin-Header hat oder einen, der keine Adresse des ProofSnap-Dashboards ist (neue Vorgänge in diesem Tarif werden nur im Dashboard erstellt; eine weiche Sperre, keine Sicherheitsgrenze). PLAN_ARCHIVE_ONLY: Fortsetzen eines Zeitplans im Tarif Archive only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url oder webhook_url zielt auf eine private, Loopback-, Link-Local- oder anderweitig gesperrte Adresse.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Automatische Aufnahmen"
        ],
        "operationId": "deleteSchedule",
        "summary": "Zeitplan löschen",
        "description": "Stoppt künftige Läufe. Bereits erstellte Aufnahmen bleiben erhalten.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Gelöscht.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "PLAN_API_READ_ONLY: ein API-Schlüssel eines Kontos im Tarif Website Archive (dessen Schlüssel ist schreibgeschützt; verwenden Sie das Dashboard).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}/runs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "Automatische Aufnahmen"
        ],
        "operationId": "listScheduleRuns",
        "summary": "Läufe eines Zeitplans auflisten",
        "description": "Die 50 neuesten von diesem Zeitplan erstellten Aufnahmen (on_change-Prüfungen, die keine Änderung gefunden haben, werden nicht aufgeführt).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Läufe.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "runs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "file_count": {
                            "type": "integer",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters": {
      "get": {
        "tags": [
          "Mandate"
        ],
        "operationId": "listMatters",
        "summary": "Mandate auflisten",
        "description": "Bis zu 200 Mandate, in denen Sie Mitglied sind. Mandate eines Unternehmens, das Sie verlassen haben, werden nicht aufgeführt.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Mandate.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matters": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Matter"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Mandate"
        ],
        "operationId": "createMatter",
        "summary": "Mandat erstellen",
        "description": "Sie werden sein Inhaber. Das Beweiskettenprotokoll beginnt mit einem Eintrag matter.created. Bei Textfeldern wird Leerraum am Anfang und Ende entfernt, sie werden auf 200 Zeichen gekürzt.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). Erforderlich. Wird auf der öffentlichen Prüfseite des Mandats angezeigt."
                  },
                  "client": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). Leer oder null bedeutet nicht gesetzt (als null gespeichert). Ein Wert, der kein String ist, wird abgelehnt.",
                    "nullable": true
                  },
                  "case_number": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). Leer oder null bedeutet nicht gesetzt (als null gespeichert). Ein Wert, der kein String ist, wird abgelehnt. Wird auf der öffentlichen Prüfseite des Mandats angezeigt.",
                    "nullable": true
                  },
                  "opposing_party": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). Leer oder null bedeutet nicht gesetzt (als null gespeichert). Ein Wert, der kein String ist, wird abgelehnt.",
                    "nullable": true
                  },
                  "jurisdiction": {
                    "type": "string",
                    "description": "Freitext. Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). Leer oder null bedeutet nicht gesetzt (als null gespeichert). Ein Wert, der kein String ist, wird abgelehnt. Wird auf der öffentlichen Prüfseite des Mandats angezeigt.",
                    "maxLength": 200,
                    "nullable": true
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Smith v. Jones",
                "case_number": "2026-CV-0142"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Erstellt.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MISSING_FIELD: name fehlt oder ist leer. MALFORMED_REQUEST: ungültiges JSON. MALFORMED_REQUEST: ein Textfeld, das kein String ist, länger als 200 Zeichen ist oder Steuer- oder Textrichtungszeichen enthält.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "get": {
        "tags": [
          "Mandate"
        ],
        "operationId": "getMatter",
        "summary": "Mandat abrufen",
        "description": "Das Mandat mit seinen Aufnahmen (bis zu 500), seinem Beweiskettenprotokoll und bis zu 100 Zugriffsereignissen. `members` liefert die E-Mail-Adresse jedes Eintrags in `member_uids`, und `actor_emails` ordnet die uids, die im Beweiskettenprotokoll und im audit_stream dieses Mandats genannt sind (Akteure sowie das Ziel von `member.added` / `member.removed`, also auch entfernte Mitglieder), ihrer E-Mail-Adresse zu; eine E-Mail-Adresse ist null oder die uid fehlt in der Zuordnung, wenn sie nicht gelesen werden kann. Nur Aufrufer, die das Mandat öffnen dürfen, erhalten diese Felder; `/matter-verify` enthält nie E-Mail-Adressen. Ein Aufrufer, der den öffentlichen Freigabelink widerrufen darf (der Inhaber des Mandats, der Inhaber eines beendeten oder archivierten Unternehmens), erhält zusätzlich `share`: `{ share_url, created_at, expires_at }` des aktiven Links (`expires_at` ist immer null: Links funktionieren bis zum Widerruf) oder null, wenn keiner existiert; andere Mitglieder erhalten das Feld nie. Einträge im audit_stream enthalten source_ip und user_agent nur für den Inhaber des Mandats; Mitglieder erhalten die Einträge ohne diese beiden Felder.\n\nEin Mandat eines beendeten Unternehmens enthält `read_only: true`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Das Mandat.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Matter"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "members": {
                          "type": "array",
                          "description": "Ein Eintrag pro Eintrag in `member_uids`, in derselben Reihenfolge.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "uid": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "nullable": true,
                                "description": "Null, wenn das Konto nicht gelesen werden kann."
                              }
                            }
                          }
                        },
                        "actor_emails": {
                          "type": "object",
                          "description": "`{ uid: email }` für die Personen, die im custody_log und im audit_stream dieses Mandats genannt sind (ohne den Systemakteur). Eine uid, deren E-Mail-Adresse nicht gelesen werden kann, fehlt.",
                          "additionalProperties": {
                            "type": "string",
                            "format": "email"
                          }
                        },
                        "captures": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MatterCapture"
                          }
                        },
                        "custody_log": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustodyEntry"
                          }
                        },
                        "audit_stream": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AuditEntry"
                          }
                        },
                        "share": {
                          "type": "object",
                          "nullable": true,
                          "description": "Nur für einen Aufrufer, der den Link widerrufen darf. Null, wenn kein aktiver Link existiert.",
                          "properties": {
                            "share_url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "Immer null: Freigabelinks laufen nicht ab."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Mandate"
        ],
        "operationId": "updateMatter",
        "summary": "Mandat aktualisieren",
        "description": "Nur für den Inhaber. Bei Textfeldern wird Leerraum am Anfang und Ende entfernt, sie werden auf 200 Zeichen gekürzt. name ist erforderlich: Wird es gesendet, muss es ein nicht leerer String sein, und ein leerer Name, ein Name nur aus Leerzeichen, null oder ein Wert, der kein String ist, wird mit 400 abgelehnt, und es wird nichts geändert. Mitglieder: add_member_email fügt eine Person hinzu, die bereits ein ProofSnap-Konto hat und zum Unternehmen des Mandats gehört; kein Konto, eine unbestätigte Adresse und ein Konto außerhalb des Unternehmens erhalten alle dieselbe Antwort 404 NOT_FOUND; remove_member_uid entfernt ein Mitglied (nicht den Inhaber). Werden beide gesendet, wird nur add_member_email angewendet. Ein Mandat hat höchstens 50 Mitglieder (ein volles Mandat lehnt ab, bevor die Adresse nachgeschlagen wird). Änderungen an Mitgliedern werden in das Beweiskettenprotokoll geschrieben. Eine Änderung von name, client, case_number, opposing_party, jurisdiction oder status wird als matter.updated in das Beweiskettenprotokoll geschrieben; dessen Ziel ist ein JSON-String mit fields (den Namen der geänderten Felder) und, wenn sich status geändert hat, dem neuen Status. Freitextwerte werden nicht in das gehashte, öffentliche Beweiskettenprotokoll geschrieben: Alte und neue Werte gehen in das Zugriffsprotokoll (audit_stream, Eintrag matter.updated). Bei einem archivierten Mandat wird eine Umbenennung oder add_member_email mit 409 MATTER_ARCHIVED abgelehnt, sofern dieselbe Anfrage das Mandat nicht wieder öffnet (status \"open\"); remove_member_uid und das Wiederöffnen funktionieren weiterhin. Jede Einladung zählt zu einem Tageslimit von 50 pro einladendem Inhaber (Unternehmenseinladungen aus dem Dashboard und add_member_email bei Mandaten zusammen, Ablehnungen eingeschlossen, da jede eine Adresse nachschlägt); bei Erreichen des Limits liefert die Anfrage 429 RATE_LIMITED. Ein serverseitiger Fehler zählt nicht.\n\nLöschsperre: `{ \"legal_hold\": true|false }` allein setzt die Löschsperre des Mandats (nur Inhaber des Mandats) und wendet sie in derselben Anfrage auf seine Aufnahmen an, mit den Wirkungen einer Löschsperre für Aufnahmen (S3 Object Lock Legal Hold, Einträge in der Beweiskette capture.legal_hold_placed / capture.legal_hold_released, die Ereigniskette der Aufnahme). Die Antwort ist das Mandat plus `changed` (das Flag hat sich geändert), `captures_held` oder `captures_released`, `skipped` ([{ capture_id, code }], z. B. eine Aufnahme im Papierkorb) und `truncated` (mehr als 1.000 Aufnahmen: Senden Sie denselben Wert erneut, um fortzufahren). Aufnahmen, die dem Mandat später zugewiesen werden, während die Löschsperre aktiv ist, werden bei der Zuweisung gesperrt. Eine Rate-Limit-Einheit pro Anfrage plus eine pro 10 geänderte Aufnahmen.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Muss mindestens ein Zeichen enthalten, das kein Leerraum ist. Weglassen, um den aktuellen Namen zu behalten. Dieselbe Regel für reinen Text: Leerraum zusammengefasst, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen oder unsichtbaren Zeichen (U+200C und U+200D sind erlaubt).",
                    "maxLength": 200
                  },
                  "client": {
                    "type": "string",
                    "nullable": true,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). null oder ein String nur aus Leerraum leert das Feld (als null gespeichert). Ein Wert, der kein String ist, wird mit 400 abgelehnt.",
                    "maxLength": 200
                  },
                  "case_number": {
                    "type": "string",
                    "nullable": true,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). null oder ein String nur aus Leerraum leert das Feld (als null gespeichert). Ein Wert, der kein String ist, wird mit 400 abgelehnt.",
                    "maxLength": 200
                  },
                  "opposing_party": {
                    "type": "string",
                    "nullable": true,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). null oder ein String nur aus Leerraum leert das Feld (als null gespeichert). Ein Wert, der kein String ist, wird mit 400 abgelehnt.",
                    "maxLength": 200
                  },
                  "jurisdiction": {
                    "type": "string",
                    "nullable": true,
                    "description": "Reiner Text: Leerraum wird zusammengefasst und am Anfang und Ende entfernt, höchstens 200 Zeichen, keine Steuerzeichen, Textrichtungszeichen (Bidi) oder unsichtbaren Zeichen (die Verbinder U+200C und U+200D, die die Schreibung von Hindi und Persisch benötigt, sind erlaubt) (sonst 400 MALFORMED_REQUEST). null oder ein String nur aus Leerraum leert das Feld (als null gespeichert). Ein Wert, der kein String ist, wird mit 400 abgelehnt.",
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "description": "Verwenden Sie POST /matters/{matter_id}/seal zum Versiegeln.",
                    "enum": [
                      "open",
                      "archived"
                    ]
                  },
                  "add_member_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "remove_member_uid": {
                    "type": "string"
                  },
                  "legal_hold": {
                    "type": "boolean",
                    "description": "Löschsperre des Mandats, allein gesendet (zusammen mit einem anderen Feld: 400). true sperrt jede aktuelle Aufnahme des Mandats und jede Aufnahme, die ihm zugewiesen wird, solange die Löschsperre aktiv ist (legal_hold_source \"matter:<id>\"; eine bereits gesperrte Aufnahme behält ihre eigene Löschsperre). false hebt nur die vom Mandat gesetzten Löschsperren auf; eine manuelle oder Massen-Löschsperre bleibt bestehen. Nur Inhaber des Mandats. Wird durch ein überfälliges Abonnement nicht blockiert. Das Setzen erfordert einen kostenpflichtigen Tarif (403 LEGAL_HOLD_NOT_AVAILABLE, details.reason plan), und Aufnahmen im Testmodus werden übersprungen (code LEGAL_HOLD_NOT_AVAILABLE in `skipped`); false allein wird auch nach Ende des Legal-Tarifs akzeptiert."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Das aktualisierte Mandat.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: name leer, nur aus Leerraum oder kein String; ein Textfeld, das kein String ist, länger als 200 Zeichen ist oder Steuer- oder Textrichtungszeichen enthält; ungültiger status; nichts zu aktualisieren; bereits Mitglied; oder Entfernen des Inhabers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (nicht der Inhaber), LEGAL_HOLD_NOT_AVAILABLE (legal_hold true ohne kostenpflichtigen Tarif) oder EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND oder NOT_FOUND (kein geeignetes Konto für die E-Mail-Adresse: keines, unbestätigt oder außerhalb des Unternehmens, eine Antwort für alle Fälle; oder remove_member_uid ist kein Mitglied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED oder MATTER_ARCHIVED (Umbenennung oder neues Mitglied bei einem archivierten Mandat). COMPANY_READ_ONLY: Das Mandat gehört einem Unternehmen, das beendet ist oder als Firmenarchiv geführt wird (Aufrufer in einem Legal-Tarif; Öffnen, Export und Widerruf der Freigabe funktionieren weiterhin).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Das Tageslimit von 50 Mitgliedereinladungen pro einladendem Inhaber wurde erreicht (Unternehmenseinladungen aus dem Dashboard und add_member_email bei Mandaten zählen zusammen; details.daily_limit, details.action \"member_invite\"). Retry-After gibt an, wann der UTC-Tag endet.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/captures": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Mandate"
        ],
        "operationId": "assignMatterCaptures",
        "summary": "Aufnahmen hinzufügen oder entfernen",
        "description": "Jedes Mitglied darf dies aufrufen, verschoben werden aber nur Aufnahmen, die der Aufrufer erstellt hat; andere werden in skipped zurückgegeben, ebenso Aufnahmen, die sich bereits in einem anderen Mandat befinden, und Aufnahmen, die sich bereits in diesem Mandat befinden (bzw. mit unassign nicht darin befinden). Einträge, die keine Aufnahme-IDs sind, werden ignoriert, und höchstens 100 werden verarbeitet. Jede Änderung wird in das Beweiskettenprotokoll geschrieben. Ein archiviertes Mandat nimmt keine neuen Aufnahmen auf (409 MATTER_ARCHIVED); unassign funktioniert weiterhin.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "unassign": {
                    "type": "boolean",
                    "description": "true entfernt die Aufnahmen aus dem Mandat.",
                    "default": false
                  }
                },
                "required": [
                  "capture_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ergebnis.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "assigned": {
                      "type": "integer",
                      "description": "Anzahl der geänderten Aufnahmen."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "unassign": {
                      "type": "boolean"
                    },
                    "legal_holds_placed": {
                      "type": "integer",
                      "description": "Vorhanden, wenn die Löschsperre des Mandats aktiv ist: auf wie viele der zugewiesenen Aufnahmen sie eine Löschsperre gesetzt hat."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: keine gültige Aufnahme-ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED oder EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED oder MATTER_ARCHIVED (Hinzufügen zu einem archivierten Mandat). COMPANY_READ_ONLY: Das Mandat gehört einem Unternehmen, das beendet ist oder als Firmenarchiv geführt wird (Aufrufer in einem Legal-Tarif; Öffnen, Export und Widerruf der Freigabe funktionieren weiterhin).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/seal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Mandate"
        ],
        "operationId": "sealMatter",
        "summary": "Mandat versiegeln",
        "description": "Nur für den Inhaber. Verankert den aktuellen Head-Hash der Beweiskette mit OpenTimestamps und, wenn in der Bereitstellung eIDAS konfiguriert ist und der Aufrufer live ist (ein Live-API-Schlüssel oder eine Dashboard-Sitzung mit verknüpfter Abrechnung), mit einem eIDAS-Zeitstempel (der eIDAS-Zeitstempel der Versiegelung wird nicht berechnet; schlägt er fehl, erfolgt die Versiegelung ohne ihn). Ein Test-Aufrufer (Testschlüssel oder Sandbox-Sitzung) wird nur mit OpenTimestamps versiegelt, und die Versiegelung hält eidas_status not_obtained mit eidas_reason test_key fest; die Anfrage hat keine eIDAS-Option, daher ist dies kein Fehler. Das Mandat ist danach schreibgeschützt: keine Bearbeitungen, keine Änderungen an Aufnahmen, und seine Aufnahmen können nicht gelöscht werden. Ein Mandat, das noch eine Aufnahme im Papierkorb enthält, wird nicht versiegelt (409 MATTER_HAS_TRASHED_CAPTURES): Stellen Sie die Aufnahme zuerst wieder her oder entfernen Sie sie aus dem Mandat.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Versiegelt.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "sealed"
                      ]
                    },
                    "seal": {
                      "$ref": "#/components/schemas/MatterSeal"
                    },
                    "custody_head_hash": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: Das Mandat hat keinen Head der Beweiskette.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif. FORBIDDEN: nicht der Inhaber. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED (bereits versiegelt), MATTER_CHANGED (das Beweiskettenprotokoll hat sich während der Versiegelung geändert; wiederholen Sie die Anfrage) oder MATTER_HAS_TRASHED_CAPTURES (eine Aufnahme des Mandats liegt im Papierkorb; stellen Sie sie zuerst wieder her oder entfernen Sie sie aus dem Mandat). COMPANY_READ_ONLY: Das Mandat gehört einem Unternehmen, das beendet ist oder als Firmenarchiv geführt wird (Aufrufer in einem Legal-Tarif; Öffnen, Export und Widerruf der Freigabe funktionieren weiterhin).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "TIMESTAMP_SERVICE_UNAVAILABLE (die Verankerung ist fehlgeschlagen, nichts wurde geändert; wiederholen Sie die Anfrage) oder CONFIGURATION_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "EIDAS_SEAL_RETRY: Der eIDAS-Zeitstempel der Versiegelung ist nicht rechtzeitig eingetroffen (oder wird noch ausgestellt). Es wurde nichts versiegelt. Wiederholen Sie die Anfrage nach Retry-After Sekunden: Die Wiederholung erhält den bereits für diesen Head der Beweiskette ausgestellten Zeitstempel, keinen zweiten. SERVICE_UNAVAILABLE: Das Gateway konnte die Anmeldedaten nicht prüfen; wiederholen Sie die Anfrage mit Backoff.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "EIDAS_SEAL_RETRY",
                    "message": "The eIDAS timestamp of the seal did not arrive in time. Nothing was sealed. Retry after the Retry-After interval; the retry reuses the timestamp already issued for this custody head.",
                    "details": {
                      "reason": "The eIDAS timestamp did not arrive in time. Nothing was sealed. Retry the seal.",
                      "retry_after_seconds": 15
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "/matters/{matter_id}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Mandate"
        ],
        "operationId": "exportMatter",
        "summary": "Mandat exportieren",
        "description": "Nur für den Inhaber. Erstellt ein ZIP mit custody_report.pdf, custody_log.json, matter.json, README.txt, access_log.json (wenn Zugriffsereignisse vorhanden sind) und dem Beweispaket jeder Aufnahme. Mit aktivierter Bates-Nummerierung (Standard) fügt es Bates-gestempelte Kopien jeder evidence.pdf unter exhibits/ hinzu (Anlagen nummeriert ab dem ältesten Beweis) und, mit formats.loadfile, ein Load-File für Concordance/Opticon. Mit formats.pdfa werden die Einreichungskopien zusätzlich in PDF/A konvertiert, wenn der Konverter verfügbar ist (bei einem synchronen Export nur innerhalb eines Zeitbudgets); nur die in pdfa_count gezählten Kopien sind PDF/A, die anderen sind PDFs ohne PDF/A-Anspruch, und README.txt erwähnt PDF/A nur, wenn pdfa_count größer als 0 ist.\n\nEin Mandat mit bis zu 20 Aufnahmen wird synchron exportiert (200). Ein größeres Mandat wird im Hintergrund exportiert: Die Antwort ist 202 mit einer job_id; fragen Sie GET /matters/{matter_id}/export?job_id= ab. Aufnahmen, die nicht in das Größenlimit passen (120 MB synchron; 800 MB und 300 Aufnahmen im Hintergrund), werden in den Berichten aufgeführt, aber nicht gebündelt.\n\nFunktioniert bei offenen und versiegelten Mandaten. Ein archiviertes Mandat wird mit 409 MATTER_ARCHIVED abgelehnt (öffnen Sie es zuerst wieder), außer für einen Aufrufer, der es nicht wieder öffnen kann: den Inhaber eines beendeten Unternehmens innerhalb seiner Aufbewahrungsfrist und ein Konto im Tarif Archive only. Jeder Export wird als export.generated im Zugriffsprotokoll festgehalten. Jeder Export zählt zu einem Tageslimit von 50 pro Benutzer; ein fehlgeschlagener Hintergrundexport gibt seine Einheit zurück.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bates": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "default": true
                      },
                      "prefix": {
                        "type": "string",
                        "description": "Nur Buchstaben und Ziffern, in Großbuchstaben umgewandelt, bis zu 12. Standard: aus dem Aktenzeichen oder dem Namen des Mandats."
                      },
                      "start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "exhibit_start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "pad": {
                        "type": "integer",
                        "minimum": 4,
                        "maximum": 8,
                        "default": 6
                      }
                    }
                  },
                  "formats": {
                    "type": "object",
                    "properties": {
                      "loadfile": {
                        "type": "boolean",
                        "description": "Das E-Discovery-Load-File einschließen (erfordert Bates-Nummerierung).",
                        "default": true
                      },
                      "pdfa": {
                        "type": "boolean",
                        "description": "Die Bates-gestempelten Einreichungskopien in PDF/A konvertieren (erfordert Bates-Nummerierung). Soweit möglich: Eine nicht konvertierte Kopie bleibt ein PDF ohne PDF/A-Anspruch; pdfa_count in der Antwort gibt an, wie viele konvertiert wurden.",
                        "default": true
                      }
                    }
                  },
                  "export_locale": {
                    "type": "string",
                    "description": "Sprache von custody_report.pdf, dem Anlagenverzeichnis und README.txt: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk (ein Bindestrich wird anstelle des Unterstrichs akzeptiert). Andere Werte fallen auf Englisch zurück."
                  },
                  "declaration": {
                    "type": "object",
                    "description": "Aus Kompatibilitätsgründen akzeptiert. Seine Textfelder (name, title, party, case_caption, executed_location) folgen der Regel für reinen Text (siehe PlainText): Ein Wert mit einem unzulässigen Zeichen wird mit 400 MALFORMED_REQUEST abgelehnt. Zu lange Werte werden gekürzt (name, title, executed_location auf 120, party auf 160, case_caption auf 200 Zeichen)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Export bereit.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "description": "Vorsignierter Download-Link, 24 Stunden gültig.",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "capture_count": {
                      "type": "integer"
                    },
                    "captures_included": {
                      "type": "integer"
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "description": "Aufnahmen, die in den Berichten aufgeführt, aber nicht gebündelt sind, weil ein Größenlimit erreicht wurde."
                    },
                    "custody_events": {
                      "type": "integer"
                    },
                    "exhibits_count": {
                      "type": "integer",
                      "description": "Bates-gestempelte Anlagenkopien."
                    },
                    "bates_prefix": {
                      "type": "string",
                      "nullable": true
                    },
                    "searchable": {
                      "type": "boolean",
                      "description": "Immer false: Den Einreichungskopien wird keine Textebene hinzugefügt. Für eine stabile Antwortstruktur beibehalten."
                    },
                    "pdfa_count": {
                      "type": "integer",
                      "description": "Einreichungskopien unter exhibits/, die PDF/A sind. 0 bedeutet, dass keine Kopie PDF/A ist und keine dies beansprucht."
                    },
                    "loadfile": {
                      "type": "boolean"
                    },
                    "size_bytes": {
                      "type": "integer"
                    },
                    "generated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Export im Hintergrund gestartet.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "async": {
                      "type": "boolean"
                    },
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "description": "Enthält die Stage /v1."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: Eine Exportoption ist ungültig (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif. FORBIDDEN: nicht der Inhaber. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: Öffnen Sie das Mandat zuerst wieder (PATCH status \"open\").",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: Der Export konnte nicht erstellt oder gestartet werden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: Der synchrone Export wurde nicht innerhalb von etwa 25 s abgeschlossen (details.reason). Wiederholen Sie die Anfrage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Mandate"
        ],
        "operationId": "getMatterExport",
        "summary": "Hintergrundexport abfragen",
        "description": "Nur für den Inhaber. Ein Job, der 30 Minuten nach seinem Start noch verarbeitet wird, wird mit dem Fehler \"export timed out\" als fehlgeschlagen gemeldet. Ein Job endet genau einmal: Ein als fehlgeschlagen gemeldeter Job wird später nie bereit, und ein bereiter Job wird nie als fehlgeschlagen gemeldet.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job-Status. Wenn bereit, sind url und die Zählwerte ausgefüllt.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed"
                      ]
                    },
                    "url": {
                      "type": "string",
                      "nullable": true
                    },
                    "captures_included": {
                      "type": "integer",
                      "nullable": true
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "nullable": true
                    },
                    "custody_events": {
                      "type": "integer",
                      "nullable": true
                    },
                    "size_bytes": {
                      "type": "integer",
                      "nullable": true
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: job_id fehlt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif. FORBIDDEN: nicht der Inhaber.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND oder NOT_FOUND für einen unbekannten Job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/share": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Mandate"
        ],
        "operationId": "shareMatter",
        "summary": "Öffentlichen Prüflink erstellen",
        "description": "Nur für den Inhaber. Liefert einen Link, den jeder öffnen kann, um Name, Aktenzeichen, Gerichtsstand, Status, Beweiskettenprotokoll und Versiegelung des Mandats zu sehen (siehe GET /matter-verify). Es werden keine Aufnahmeinhalte geteilt. Ein erneuter Aufruf liefert denselben Link. Der Link funktioniert, bis Sie ihn widerrufen.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Freigabelink.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "share_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: Der Tarif ist kein Legal-Tarif. FORBIDDEN: nicht der Inhaber. EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: Ein archiviertes Mandat wird nicht geteilt. COMPANY_READ_ONLY: Das Mandat gehört einem Unternehmen, das beendet ist oder als Firmenarchiv geführt wird (Aufrufer in einem Legal-Tarif; Öffnen, Export und Widerruf der Freigabe funktionieren weiterhin).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Mandate"
        ],
        "operationId": "revokeMatterShare",
        "summary": "Öffentlichen Link widerrufen",
        "description": "Nur für den Inhaber. Der Link funktioniert nicht mehr. Ein späterer POST erstellt einen neuen. Der Inhaber eines beendeten Unternehmens (oder eines, das als Firmenarchiv geführt wird) und der Inhaber eines Mandats im Tarif Archive only dürfen den Link ohne Legal-Tarif widerrufen (das Erstellen bleibt eine Funktion des Legal-Tarifs).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Widerrufen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Live-Aufrufer, dessen Abonnement (oder Unternehmen) überfällig oder unbezahlt ist."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (nicht der Inhaber) oder EMAIL_NOT_VERIFIED: eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, oder ein API-Schlüssel eines solchen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Öffentlich"
        ],
        "operationId": "health",
        "summary": "Health-Check",
        "description": "Immer 200, solange die API antwortet. Mit deep=1 (oder true) ergänzt die Antwort dependencies: ob jeder nachgelagerte Dienst konfiguriert ist, und für eIDAS eine Live-Prüfung des Zeitstempeldienstes. Nur eIDAS wird geprüft; die anderen melden nur ihre Konfiguration. status wird degraded, wenn eIDAS konfiguriert ist und seine Prüfung fehlschlägt. Eine Prüfung, die nicht rechtzeitig antwortet, wird in dependencies gemeldet (probe_timeout oder eidas.error timeout), nie als fehlgeschlagener /health. deep=1 unterliegt einem Rate-Limit pro Client-IP (10 pro Minute): Darüber liefert die Anfrage 429 RATE_LIMITED mit Retry-After.",
        "security": [],
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "stage": {
                      "type": "string"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "uptime_ms": {
                      "type": "integer",
                      "description": "Millisekunden seit dem Start der bedienenden Instanz."
                    },
                    "version": {
                      "type": "string"
                    },
                    "dependencies": {
                      "type": "object",
                      "properties": {
                        "eidas": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            },
                            "healthy": {
                              "type": "boolean",
                              "description": "null, wenn nicht konfiguriert.",
                              "nullable": true
                            },
                            "error": {
                              "type": "string",
                              "description": "Fester Code, wenn die eIDAS-Prüfung keinen fehlerfreien Zustand gemeldet hat: timeout (keine Antwort innerhalb von 15 s), unavailable (der Zeitstempeldienst meldete einen Fehlerzustand), error (die Prüfung konnte nicht ausgeführt werden). Details werden serverseitig protokolliert, nie zurückgegeben.",
                              "enum": [
                                "timeout",
                                "unavailable",
                                "error"
                              ]
                            }
                          }
                        },
                        "ots": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "c2pa_sign": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "ntp": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "whois": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "error": {
                          "type": "string",
                          "description": "Nur wenn die Prüfung der Abhängigkeiten selbst fehlgeschlagen ist: probe_timeout, wenn sie nicht innerhalb von 20 s geantwortet hat, sonst probe_failed. Die Antwort ist trotzdem 200."
                        }
                      },
                      "description": "Nur mit deep=1."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ledger/{hash}": {
      "get": {
        "tags": [
          "Öffentlich"
        ],
        "operationId": "ledgerLookup",
        "summary": "Manifest-Hash nachschlagen",
        "description": "Prüft, ob ProofSnap die Ausstellung eines Pakets festgehalten hat, dessen manifest.json diesen SHA-256 hat. Aufnahmen, Zertifikate, Vergleiche und Schwärzungen werden bei ihrer Ausstellung festgehalten, mit dem Hash des ausgelieferten Manifests (nach dem Schritt des qualifizierten Zeitstempels); Pakete, die ausgestellt wurden, bevor das Ausstellungsregister existierte, sind nicht darin enthalten. issued: false bedeutet nur, dass kein Eintrag vorhanden ist.",
        "security": [],
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "SHA-256 von manifest.json, 64 Hexadezimalzeichen, beliebige Groß- und Kleinschreibung, optional mit dem Präfix sha256:.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ergebnis der Abfrage.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "manifest_sha256": {
                      "type": "string"
                    },
                    "issued": {
                      "type": "boolean"
                    },
                    "issued_at": {
                      "type": "string",
                      "nullable": true,
                      "format": "date-time",
                      "description": "Fehlt, wenn issued false ist."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "capture",
                        "certify",
                        "compare",
                        "redaction"
                      ],
                      "nullable": true,
                      "description": "Fehlt, wenn issued false ist."
                    },
                    "eidas": {
                      "type": "boolean",
                      "nullable": true,
                      "description": "Ob das ausgestellte Paket einen qualifizierten eIDAS-Zeitstempel enthält (manifest.json.tsr). Fehlt, wenn issued false ist; null bei Einträgen, die vor dem 2026-10-01 geschrieben wurden."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Der Hash besteht nicht aus 64 Hexadezimalzeichen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matter-verify": {
      "get": {
        "tags": [
          "Öffentlich"
        ],
        "operationId": "matterVerify",
        "summary": "Öffentliche Ansicht eines geteilten Mandats",
        "description": "Schreibgeschützte Daten hinter einem Freigabelink eines Mandats: Mandatsdetails, das Beweiskettenprotokoll (damit sich die Hash-Kette nachrechnen lässt) und die Versiegelung mit ihrem OpenTimestamps-Nachweis und eIDAS-Token. Keine Aufnahmeinhalte, Download-Links oder Zugriffsprotokolle. attested_by.organization ist der vom Inhaber festgelegte Name des Unternehmens, zu dem das Mandat gehört (ein Unternehmensmandat), sonst null; ein persönliches Mandat nennt keine Organisation. custody_log[].actor_org ist das Unternehmen, dem der Handelnde angehörte, als dieser Eintrag geschrieben wurde (mit dem Eintrag gespeichert, außerhalb der gehashten Nutzdaten), nur bei einem Unternehmensmandat; Einträge, die geschrieben wurden, bevor dies erfasst wurde, zeigen null. Rate-Limit pro Client-IP (standardmäßig 30 Anfragen pro 60 Sekunden).",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mandatsansicht.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "case_number": {
                          "type": "string",
                          "nullable": true
                        },
                        "jurisdiction": {
                          "type": "string",
                          "nullable": true
                        },
                        "status": {
                          "type": "string"
                        },
                        "sealed": {
                          "type": "boolean"
                        },
                        "attested_by": {
                          "type": "object",
                          "properties": {
                            "organization": {
                              "type": "string",
                              "nullable": true
                            }
                          }
                        },
                        "capture_count": {
                          "type": "integer"
                        },
                        "custody_count": {
                          "type": "integer"
                        },
                        "custody_head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "seal": {
                          "type": "object",
                          "properties": {
                            "root_hash": {
                              "type": "string"
                            },
                            "ots_present": {
                              "type": "boolean"
                            },
                            "eidas_present": {
                              "type": "boolean"
                            },
                            "ots_hex": {
                              "type": "string",
                              "nullable": true
                            },
                            "ots_pending": {
                              "type": "boolean"
                            },
                            "tsr_base64": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_serial": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_issued_at": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_ltv": {
                              "type": "boolean"
                            },
                            "sealed_at": {
                              "type": "string"
                            },
                            "eidas_status": {
                              "type": "string",
                              "enum": [
                                "confirmed",
                                "not_obtained",
                                "not_configured"
                              ],
                              "nullable": true,
                              "description": "null bei Versiegelungen vor dem 2026-10-01 ohne eIDAS-Token."
                            },
                            "eidas_reason": {
                              "type": "string",
                              "enum": [
                                "token_refused",
                                "unavailable",
                                "test_key",
                                "account_limit"
                              ],
                              "description": "Nur wenn eidas_status den Wert not_obtained hat. test_key: Die Versiegelung wurde von einem Test-Aufrufer vorgenommen, daher wurde kein eIDAS-Zeitstempel angefordert. account_limit: Das Konto hat noch keine bezahlte Rechnung und hat sein eIDAS-Limit erreicht (20 pro UTC-Tag, 50 pro UTC-Monat), daher wurde kein eIDAS-Zeitstempel angefordert."
                            },
                            "eidas_verification_status": {
                              "type": "string",
                              "nullable": true,
                              "description": "verified, wenn das Token bei der Versiegelung die ProofSnap-Prüfung bestanden hat."
                            },
                            "eidas_revocation_status": {
                              "type": "string",
                              "nullable": true,
                              "enum": [
                                "good",
                                "revoked_after_gen_time",
                                "unknown",
                                "not_checked",
                                null
                              ],
                              "description": "Status des TSA-Zertifikats, ermittelt bei der Versiegelung (nicht live abgerufen)."
                            },
                            "eidas_validation": {
                              "type": "object",
                              "nullable": true,
                              "additionalProperties": true,
                              "description": "Der bei der Versiegelung gespeicherte Inhalt von eidas_validation.json (TSA-Kette, fest hinterlegtes Stammzertifikat, OCSP-Antwort, CRL, Prüfprotokoll), damit das Token in tsr_base64 offline geprüft werden kann. null bei älteren Versiegelungen."
                            }
                          },
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string"
                        }
                      }
                    },
                    "custody_log": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/CustodyEntry"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "actor_org": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        ]
                      }
                    },
                    "issuer": {
                      "type": "string",
                      "enum": [
                        "ProofSnap"
                      ]
                    },
                    "verified_at": {
                      "type": "string",
                      "nullable": true,
                      "description": "Immer null."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: ungültiges, unbekanntes oder widerrufenes Token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/captures/export-jobs": {
      "post": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "startCaptureExportJob",
        "summary": "Alle Aufnahmen in einem Download exportieren (asynchroner Job)",
        "description": "Startet einen Job, der alle Ihre Aufnahmen in einen Download packt, aufgeteilt in Teile von höchstens etwa 2 GB. Jeder Teil ist ein einfaches ZIP: README.txt (Englisch) plus README.<language>.txt (derselbe Text übersetzt, wenn language nicht en ist; beide UTF-8 mit Byte Order Mark und CRLF-Zeilenenden; Zeile 1 jeder Datei verweist auf die andere), captures.csv (UTF-8 mit Byte Order Mark, CRLF; jede Aufnahme des Exports mit den Spalten member (die E-Mail-Adresse des Erstellers in einem Unternehmensexport, leer in Ihrem eigenen Export), capture_id, created_at (wann die Anfrage eingegangen ist), captured_at (der Zeitpunkt der Aufnahme; leer bei einer fehlgeschlagenen Aufnahme), url, status (completed, failed oder unavailable), export_part, file, ots_file (captures/<capture_id>.upgraded.ots, wenn dieser Teil den Nachweis enthält, sonst leer), test_mode (true für eine mit einem Testschlüssel erstellte Aufnahme: mit Wasserzeichen, kein Beweismittel) und note (warum ein Paket nicht enthalten ist, auch für jede Zeile mit unavailable); dieselbe Liste in jedem Teil, und wenn Teile voneinander abweichen, ist die Liste des höchsten Teils maßgeblich) sowie captures/<capture_id>.zip, das originale versiegelte Beweispaket Byte für Byte, plus captures/<capture_id>.upgraded.ots, wenn ein aktualisierter OpenTimestamps-Nachweis gespeichert ist. Jedes innere Paket wird für sich mit seinem verification/verify.sh oder verify.ps1 geprüft. Ein Paket über 2 GB wird nicht aufgenommen (als unavailable mit Hinweis aufgeführt; laden Sie es einzeln mit GET /captures/{capture_id} herunter). Fehlgeschlagene Aufnahmen werden nur in captures.csv aufgeführt; Aufnahmen im Papierkorb und endgültig gelöschte Aufnahmen werden nicht exportiert. Die Einträge jedes Teils tragen den Exportzeitpunkt als Datum (UTC, auch im erweiterten Zeitstempelfeld). Fragen Sie GET /captures/export-jobs/{job_id} ab, bis status den Wert ready hat. **Wiederverwendung:** Wenn Ihr letzter Export bereit ist, denselben Umfang (und dieselbe org) und dieselbe Sprache hat, noch mindestens einen Tag seines Download-Zeitfensters übrig hat, Ihre Rechte unverändert sind und seit seiner Planung keine Aufnahme hinzugefügt, abgeschlossen, in den Papierkorb verschoben, wiederhergestellt oder endgültig gelöscht wurde, gibt die Anfrage 200 mit diesem Job zurück (reused: true): Nichts wird neu erstellt und kein Tageskontingent verbraucht. Ein neuerer bereiter Export desselben Umfangs ersetzt den vorherigen (der vorherige Job wird expired, expired_reason superseded). Wer ihn starten darf: ein Live-API-Schlüssel oder die Dashboard-Sitzung, auf jedem Tarif einschließlich Sandbox, Archive only und schreibgeschützter Schlüssel (Website Archive). Ein Testschlüssel erhält 403 FORBIDDEN (der Export enthält Live-Aufnahmen), und GET /captures/export-jobs/{job_id} gibt einem Testschlüssel für jeden Job 404 EXPORT_JOB_NOT_FOUND zurück. Ihr eigener Export (scope own) enthält Ihre persönlichen Aufnahmen und die, die Sie für Ihr aktuelles Unternehmen oder ein Unternehmen in Ihrem Besitz erstellt haben, auch über einen Schlüssel, solange dieses Unternehmen ein laufendes Firmenarchiv ist (die Aufnahmen der anderen Plätze eines Archivs sind nur über die Dashboard-Sitzung erreichbar). scope company (nur der Inhaber des Unternehmens, auch für ein beendetes Unternehmen innerhalb seines Download-Zeitfensters; ein laufendes Firmenarchiv nur über die Dashboard-Sitzung) exportiert die für das Unternehmen erstellten Aufnahmen aller Plätze plus Ihre eigenen, mit der E-Mail-Adresse des Erstellers in der Spalte member. Ein laufender Job pro Konto (409 EXPORT_JOB_RUNNING nennt ihn), 5 gestartete Jobs pro Konto und UTC-Tag (ein fehlgeschlagener Job gibt seine Einheit zurück, ein wiederverwendeter verbraucht keine), das Rate-Limit pro Minute Ihres Tarifs. Höchstens die neuesten 20.000 Aufnahmen pro Job (truncated).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "own",
                      "company"
                    ],
                    "default": "own"
                  },
                  "org": {
                    "type": "string",
                    "description": "Mit scope company: eines der Unternehmen in Ihrem Besitz (aktuelles oder letztes), wenn Ihnen beide gehören."
                  },
                  "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Sprache der README-Übersetzung: README.<language>.txt (die vollständige Übersetzung) neben der englischen README.txt in jedem Teil; en liefert nur README.txt. Einer der Werte: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Groß- und Kleinschreibung, Bindestrich und Region werden toleriert (\"PT-BR\" ist pt_BR); ein bloßes \"pt\" und \"zh\" und alles andere ergeben 400 MALFORMED_REQUEST. Ein bereiter Export wird nur für dieselbe Sprache wiederverwendet. Das Dashboard sendet seine Oberflächensprache."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ihr bereiter Export ist noch aktuell und wird wiederverwendet: kein neuer Job, keine Tageseinheit. Lesen Sie seine Links mit GET poll_path.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path",
                    "reused"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "ready"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "reused": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Der Job wurde gestartet.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: Der Body ist kein JSON-Objekt, oder scope / org ist ungültig.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: Die Anfrage wurde mit einem Testschlüssel gestellt, oder scope company von jemandem, der nicht Inhaber eines Unternehmens innerhalb seines Download-Zeitfensters ist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "EXPORT_JOB_RUNNING: Ein Job des Kontos wird noch verarbeitet; details.job_id und details.poll_path nennen ihn.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/export-jobs/{job_id}": {
      "get": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "getCaptureExportJob",
        "summary": "Status und Download-Links eines Aufnahme-Export-Jobs",
        "description": "Status und Fortschritt des Jobs; sobald er bereit ist, ein vorsignierter Link pro Teil. Ein Link ist bis url_expires_at gültig: höchstens 24 Stunden, nie über download_expires_at hinaus und nie über die Anmeldedaten hinaus, mit denen er signiert wurde (das kann weniger als eine Stunde sein); fragen Sie nach url_expires_at erneut ab, um neue Links zu erhalten. download_expires_at liegt 7 Tage nach der UTC-Mitternacht vor dem Start des ersten Teils des Jobs (6 bis 7 Tage, nachdem der Job bereit wurde); danach werden die Teile gelöscht. Der Job verliert seinen Download vorzeitig (status expired, die Teile gelöscht, expired_reason), wenn ein neuerer Export desselben Umfangs ihn ersetzt hat, eine enthaltene Aufnahme endgültig gelöscht wurde, die Daten seines Unternehmens gelöscht wurden oder Ihre Rechte ihn nicht mehr abdecken (bei jedem Lesen eines bereiten Jobs geprüft). Ein Job, der 30 Minuten lang keinen Fortschritt meldet, wird durch diesen Aufruf als failed beendet und was er geschrieben hat, wird gelöscht. Die erste Antwort mit Links innerhalb einer Stunde wird im Audit-Log erfasst (capture.export_job.links_issued). Nur das Konto, das den Job gestartet hat, sieht ihn; einen Unternehmensexport, der ein Firmenarchiv enthält, nur über die Dashboard-Sitzung.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cexp_[A-Za-z0-9]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Der Job.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "parts"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1",
                      "pattern": "^cexp_[A-Za-z0-9]{16}$"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed",
                        "expired"
                      ],
                      "description": "processing, solange der Plan und die Teile geschrieben werden; ready mit den Download-Links; failed (starten Sie einen neuen Job, die Tageseinheit wurde zurückgegeben); expired, wenn das Download-Zeitfenster vorbei ist oder der Download entzogen wurde (expired_reason; die Teile sind gelöscht)."
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "language": {
                      "type": "string",
                      "enum": [
                        "en",
                        "de",
                        "it",
                        "fr",
                        "es",
                        "pt_BR",
                        "cs",
                        "el",
                        "hi",
                        "hu",
                        "id",
                        "ja",
                        "ko",
                        "nl",
                        "pl",
                        "ro",
                        "ru",
                        "sv",
                        "tr",
                        "vi",
                        "zh_CN",
                        "zh_TW",
                        "ar",
                        "he",
                        "da",
                        "fi",
                        "nb",
                        "sk",
                        "uk"
                      ],
                      "description": "Die README-Sprache des Jobs (README.<language>.txt neben README.txt; en nur für README.txt)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "ended_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "progress": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "captures_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "captures_done": {
                          "type": "integer"
                        },
                        "parts_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "parts_done": {
                          "type": "integer"
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true, wenn das Konto mehr als 20.000 exportierbare Aufnahmen hat: Der Job enthält die neuesten 20.000."
                    },
                    "counts": {
                      "type": "object",
                      "nullable": true,
                      "description": "Gesetzt, wenn der Job bereit ist.",
                      "properties": {
                        "captures": {
                          "type": "integer",
                          "description": "Zeilen von captures.csv."
                        },
                        "packaged": {
                          "type": "integer",
                          "description": "Pakete in den Teilen."
                        },
                        "failed": {
                          "type": "integer",
                          "description": "Fehlgeschlagene Aufnahmen (kein Paket, nur in captures.csv aufgeführt)."
                        },
                        "unavailable": {
                          "type": "integer",
                          "description": "Nicht aufgenommene Pakete: Sie konnten während des Exports nicht gelesen werden oder sind größer als 2 GB (captures.csv vermerkt, welche)."
                        }
                      }
                    },
                    "parts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "part",
                          "bytes",
                          "count",
                          "download_url"
                        ],
                        "properties": {
                          "part": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 1,
                            "description": "Nummer des Teils, 1 bis zur Anzahl der Teile."
                          },
                          "bytes": {
                            "type": "integer",
                            "example": 1834201112,
                            "description": "Größe des ZIP-Teils in Bytes."
                          },
                          "count": {
                            "type": "integer",
                            "example": 412,
                            "description": "Aufnahmepakete in diesem Teil."
                          },
                          "filename": {
                            "type": "string",
                            "example": "proofsnap-captures-2026-10-07T1430Z-part-1-of-3.zip",
                            "description": "Dateiname, unter dem der Download gespeichert wird (Content-Disposition): proofsnap-captures-<UTC-Datum und -Uhrzeit des Jobstarts>, mit -part-N-of-M, wenn es mehrere Teile gibt."
                          },
                          "download_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true,
                            "description": "Vorsigniertes GET des ZIP-Teils, gültig bis url_expires_at. null, wenn der Job abgelaufen ist."
                          }
                        }
                      }
                    },
                    "download_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Ende des Download-Zeitfensters: 7 Tage nach der UTC-Mitternacht vor dem Start des ersten Teils (der Speicher-Lebenszyklus löscht die Teile danach)."
                    },
                    "url_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Wann die download_url-Werte dieser Antwort nicht mehr funktionieren: höchstens 24 Stunden, nie über download_expires_at hinaus, nie über die signierenden Anmeldedaten hinaus. Fragen Sie danach erneut ab, um neue Links zu erhalten."
                    },
                    "error": {
                      "type": "string",
                      "nullable": true,
                      "example": "export timed out"
                    },
                    "expired_reason": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "window_ended",
                        "superseded",
                        "rights_changed",
                        "capture_erased",
                        "company_deleted",
                        null
                      ],
                      "description": "Gesetzt, wenn status den Wert expired hat: window_ended (download_expires_at ist verstrichen), superseded (ein neuerer Export desselben Umfangs), rights_changed (Ihre Rechte decken den Job nicht mehr ab), capture_erased (eine enthaltene Aufnahme wurde endgültig gelöscht), company_deleted (die Daten seines Unternehmens wurden gelöscht)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "EXPORT_JOB_NOT_FOUND: kein solcher Job, der Job eines anderen Kontos, sein Datensatz ist abgelaufen (8 Tage nach seinem Ende), oder die Anfrage wurde mit einem Testschlüssel gestellt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookSubscriptions",
        "summary": "Webhook-Abonnements auflisten",
        "description": "Die Webhook-Abonnements des Kontos, neueste zuerst, aktive und deaktivierte, ohne Secrets. API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Abonnements.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhookSubscription",
        "summary": "Kontoereignisse abonnieren",
        "description": "Abonnieren Sie mit einem https-Endpunkt Kontoereignisse: Jedes passende Ereignis des Kontos wird per POST an ihn gesendet, signiert mit dem eigenen Secret des Abonnements (einmalig in dieser Antwort zurückgegeben). Erfordert eine bestätigte E-Mail-Adresse (403 EMAIL_NOT_VERIFIED). Erlaubt, während ein Abonnement überfällig ist (es wird nichts berechnet). Limits für AKTIVE Abonnements pro Konto: 25 auf developer, startup, growth, custom, legal_solo und legal_seats, 10 auf website_archive, 0 auf archive_only und sandbox (409 WEBHOOK_SUBSCRIPTION_LIMIT). Die URL folgt den webhook_url-Regeln (https, keine Anmeldedaten, keine private oder reservierte Adresse; bei jeder Zustellung erneut geprüft). Idempotency-Key wird unterstützt: Eine Wiederholung mit demselben Schlüssel und Body innerhalb von 24 Stunden erhält die erste Antwort zurück, einschließlich Secret, statt eines zweiten Abonnements. API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "example": "https://hooks.example.com/proofsnap"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Reiner Text, höchstens 200 Zeichen."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Erstellt. `secret` wird nur hier angezeigt.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionCreated"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (events, filters, description, unbekanntes Feld), MISSING_FIELD (url) oder INVALID_URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT, IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED (die URL verweist auf eine gesperrte Adresse) oder IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions/{subscription_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SubscriptionId"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhookSubscription",
        "summary": "Webhook-Abonnement abrufen",
        "description": "Ein Abonnement mit seinem Zustellstatus, ohne das Secret. API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Das Abonnement.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: unbekannt, gelöscht oder das eines anderen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhookSubscription",
        "summary": "Webhook-Abonnement ändern oder reaktivieren",
        "description": "Ändern Sie events, filters (null oder {} leert sie) oder description, oder reaktivieren Sie ein deaktiviertes Abonnement mit status \"active\" (setzt die Fehlerzähler zurück; wird gegen das Limit aktiver Abonnements geprüft). Die URL kann nicht geändert werden: Erstellen Sie ein neues Abonnement. Erfordert eine bestätigte E-Mail-Adresse. API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                      }
                    ],
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Das geänderte Abonnement.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: unbekannt, gelöscht oder das eines anderen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT (Reaktivierung über dem Limit).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhookSubscription",
        "summary": "Webhook-Abonnement löschen",
        "description": "Löscht das Abonnement, sein Secret und seine wartenden Zustellungen. 404 bei einem unbekannten oder bereits gelöschten Abonnement (betrachten Sie es als entfernt). API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Gelöscht.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: unbekannt, gelöscht oder das eines anderen Kontos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listEvents",
        "summary": "Ereignisprotokoll",
        "description": "Jedes für das Konto in den letzten 30 Tagen ausgelöste Ereignis, unabhängig davon, ob ein Abonnement es empfangen hat, neueste zuerst. Ein API-Schlüssel sieht die Ereignisse der Umgebung, in der er läuft (ein Live-API-Schlüssel Live-Ereignisse, ein Testschlüssel Test-Ereignisse); eine Dashboard-Sitzung sieht Live-Ereignisse. Verwenden Sie es zum Nachladen oder zum Abfragen statt eines Abonnements. API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Nur dieser Ereignistyp.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 bis 100, Standard 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Eine Ereignis-ID: Die Seite beginnt nach ihr (übergeben Sie next_cursor). Eine ID eines anderen Kontos oder einer anderen Umgebung oder eine ältere als 30 Tage ergibt 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eine Seite mit Ereignissen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Als starting_after für die nächste Seite übergeben; null auf der letzten Seite."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (type, limit oder starting_after).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "whoami",
        "summary": "Wer bin ich",
        "description": "Was die Anmeldedaten sind: Tarif, Umgebung, ob der API-Schlüssel schreibgeschützt ist, sowie ID, Name und Präfix des Schlüssels (null bei einer Dashboard-Sitzung). Integrationen verwenden dies, um eine Verbindung zu testen und zu beschriften. Keine E-Mail-Adresse und keine Konto-ID. API-Schlüssel oder Dashboard-Sitzung; ein schreibgeschützter Schlüssel (Website Archive) darf ihn verwenden.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Die Anmeldedaten.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "string",
                      "nullable": true,
                      "example": "startup",
                      "enum": [
                        "developer",
                        "startup",
                        "growth",
                        "custom",
                        "legal_solo",
                        "legal_seats",
                        "website_archive",
                        "archive_only",
                        "sandbox",
                        null
                      ]
                    },
                    "api_read_only": {
                      "type": "boolean",
                      "description": "true für einen API-Schlüssel eines Website-Archive-Kontos (er darf lesen und abonnieren, aber keine Aufnahmen erstellen)."
                    },
                    "env": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ],
                      "description": "Die Umgebung, in der die Anmeldedaten laufen."
                    },
                    "key": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "key_id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "prefix": {
                          "type": "string",
                          "enum": [
                            "ps_live_",
                            "ps_test_"
                          ]
                        }
                      }
                    },
                    "email_verified": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/events": {
      "get": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "listCaptureEvents",
        "summary": "Ereigniskette der Aufnahmen (Löschungen und Löschsperren)",
        "description": "Die manipulationssichere Aufzeichnung des Kontos darüber, was mit seinen Aufnahmen geschehen ist: Papierkorb, Wiederherstellung, endgültige Löschung (erase), Leeren des Papierkorbs, Löschung nach Aufbewahrungsfrist, Löschung mit dem Konto oder dem Unternehmen, gesetzte und aufgehobene Löschsperre (eine Aufhebung durch das System nach Ende des Abonnements hat den Handelnden `system` und das Detail `subscription_ended`), das Ergebnis von S3 Object Lock sowie der Import eines Beweispakets (Typ `import`, Detail = die Paketart). Älteste zuerst. Die Kette überdauert die Aufnahmen und die Daten des Kontos: Sie enthält nur IDs und Manifest-Hashes, keine Aufnahmeinhalte und keinen Freitext.\n\nSo prüfen Sie sie: (1) Berechnen Sie für jedes Ereignis SHA-256 über das kanonische JSON der gehashten Felder (verification.hashed_fields; Schlüssel sortiert, kein Leerraum, fehlende Werte als null) und vergleichen Sie es mit `hash`; (2) prüfen Sie, dass jeder `prev_hash` dem `hash` des vorherigen Ereignisses entspricht (64 Nullen für seq 1) und dass seq keine Lücken hat; (3) prüfen Sie bei einem Anker, dass `head_hash` dem `hash` des Ereignisses `to_seq` entspricht, schreiben Sie `ots_proof_base64` (base64-dekodiert) in eine Datei und führen Sie `ots verify -d <head_hash> anchor.ots` aus (OpenTimestamps-Client): Dies beweist, dass die Kette bis zu diesem Ereignis spätestens zum Zeitpunkt des Bitcoin-Blocks existierte. Anker werden einmal täglich für jedes Konto mit Ereignissen eines früheren Tages erstellt.\n\nAPI-Schlüssel oder Dashboard-Sitzung, das eigene Konto; `scope=company` gibt einem Unternehmensinhaber die Kette der Aufnahmen des Unternehmens (`org` wählt eines aus, wenn dem Aufrufer mehrere gehören; ein als Firmenarchiv geführtes Unternehmen nur über die Dashboard-Sitzung). Ein Testschlüssel erhält 403 FORBIDDEN: Die Kette umfasst Live-Aufnahmen und kann nicht gefiltert werden, ohne sie zu brechen.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 bis 500, Standard 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Eine Ereignis-seq: Die Seite beginnt danach (übergeben Sie next_starting_after).",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "own",
                "company"
              ],
              "default": "own"
            }
          },
          {
            "name": "org",
            "in": "query",
            "required": false,
            "description": "scope=company: die Unternehmens-ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eine Seite der Kette.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "chain": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "head": {
                      "type": "object",
                      "properties": {
                        "chain": {
                          "type": "string"
                        },
                        "seq": {
                          "type": "integer"
                        },
                        "head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "anchored_seq": {
                          "type": "integer"
                        }
                      }
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEvent"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "integer",
                      "nullable": true
                    },
                    "anchors": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEventAnchor"
                      }
                    },
                    "verification": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string"
                        },
                        "hashed_fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "genesis_prev_hash": {
                          "type": "string"
                        },
                        "anchor": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: limit, starting_after oder scope ungültig.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: Die Anfrage wurde mit einem Testschlüssel gestellt, oder scope=company von jemandem, dem das Unternehmen nicht gehört.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/legal-holds": {
      "post": {
        "tags": [
          "Aufnahmen"
        ],
        "operationId": "bulkLegalHold",
        "summary": "Löschsperren für mehrere Aufnahmen setzen oder aufheben",
        "description": "Setzt (enabled true) oder hebt (false) die Löschsperre vieler Aufnahmen auf einmal auf. selector enthält genau eines von: `capture_ids` (1 bis 500 IDs), `matter_id` (die Aufnahmen eines Mandats, das der Aufrufer öffnen kann), `host` (die eigenen Aufnahmen des Aufrufers von diesem Hostnamen) oder `schedule_id` (die eigenen Aufnahmen des Aufrufers, die von einem seiner Zeitpläne erstellt wurden). Jede Aufnahme durchläuft die Regeln von PATCH /captures/{capture_id} mit action legal_hold und erhält ein eigenes Ergebnis: applied, unchanged (bereits in diesem Zustand) oder abgelehnt mit dem Fehlercode (z. B. FORBIDDEN, CAPTURE_IN_TRASH, CAPTURE_PROCESSING, LEGAL_HOLD_OWNER_ONLY, LEGAL_HOLD_MATTER, LEGAL_HOLD_SOURCE für eine Schwärzung, deren Quellaufnahme noch gesperrt ist, CAPTURE_NOT_FOUND, LEGAL_HOLD_NOT_AVAILABLE für eine Aufnahme im Testmodus). Die aus jeder Aufnahme erstellten Schwärzungen folgen ihrer Sperre, auch bei einer als unchanged gemeldeten Aufnahme; wird dies nicht innerhalb der Anfrage fertig, wird es binnen Minuten abgeschlossen. Das Setzen von Sperren erfordert einen kostenpflichtigen Tarif: Die kostenlose Sandbox erhält für die gesamte Anfrage 403 LEGAL_HOLD_NOT_AVAILABLE (details.reason \"plan\"); das Aufheben funktioniert auf jedem Tarif. Eine hier gesetzte Sperre hat legal_hold_source \"bulk\"; sie lässt sich gesammelt oder einzeln gleichermaßen aufheben. Höchstens 500 Aufnahmen pro Anfrage: Ein Mandat, Host oder Zeitplan mit mehr wird abgeschnitten (`truncated: true`, senden Sie die Anfrage erneut). Die S3-Object-Lock-Löschsperre der ersten 25 geänderten Aufnahmen wird in der Anfrage gesetzt, der Rest binnen Minuten (`s3_legal_hold.status` pending). Kosten: eine Rate-Limit-Einheit pro 10 Aufnahmen (mindestens eine); eine Anfrage, die mehr benötigt, als ein Zeitfenster erlaubt, ergibt 400 RATE_LIMIT_COST_TOO_HIGH. API-Schlüssel oder Dashboard-Sitzung; auf Website Archive ist der API-Schlüssel schreibgeschützt (403 PLAN_API_READ_ONLY). Eine Dashboard-Sitzung oder ein Schlüssel, deren E-Mail-Adresse nicht bestätigt ist, erhält 403 EMAIL_NOT_VERIFIED. Wird durch ein überfälliges Abonnement nicht blockiert.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled",
                  "selector"
                ],
                "additionalProperties": false,
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Reiner Text, der mit jeder gesetzten Sperre gespeichert wird (nicht mit einer Aufhebung); nie in den gehashten Ketten."
                  },
                  "selector": {
                    "type": "object",
                    "minProperties": 1,
                    "maxProperties": 1,
                    "properties": {
                      "capture_ids": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 500,
                        "items": {
                          "type": "string"
                        }
                      },
                      "matter_id": {
                        "type": "string"
                      },
                      "host": {
                        "type": "string"
                      },
                      "schedule_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ergebnisse pro Aufnahme.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "selector": {
                      "type": "string",
                      "enum": [
                        "capture_ids",
                        "matter_id",
                        "host",
                        "schedule_id"
                      ]
                    },
                    "requested": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "applied": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    },
                    "refused": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "result": {
                            "type": "string",
                            "enum": [
                              "applied",
                              "unchanged",
                              "refused"
                            ]
                          },
                          "code": {
                            "type": "string",
                            "description": "Der Fehlercode einer Ablehnung."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (Body, selector, mehr als 500 capture_ids), RATE_LIMIT_COST_TOO_HIGH.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED, PLAN_API_READ_ONLY, LEGAL_HOLD_NOT_AVAILABLE (Setzen ohne kostenpflichtigen Tarif, details.reason plan).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND (matter_id, die der Aufrufer nicht öffnen kann), SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports": {
      "post": {
        "tags": [
          "Paketimport"
        ],
        "operationId": "createImport",
        "summary": "Paketimport starten",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Erstellt einen Import und startet seinen Upload. Senden Sie die exakte Größe des ZIP in Bytes; die Antwort enthält eine vorsignierte PUT-URL pro 32-MiB-Teil (der letzte kleiner), gültig für 1 Stunde (`upload.expires_at`; nie länger als die Signatur-Anmeldedaten des Servers). Senden Sie die Bytes jedes Teils per PUT an seine URL, bewahren Sie den `ETag`-Header jeder Antwort auf und rufen Sie dann innerhalb von 24 Stunden POST /imports/{import_id}/complete auf. Mit `matter_id` wird die angenommene Aufnahme diesem Mandat hinzugefügt: Sie müssen ihm jetzt Aufnahmen hinzufügen können (ein offenes Mandat, das Sie öffnen können; der Import eines Platzinhabers des Unternehmens nur in ein Mandat dieses Unternehmens). Jedes Erstellen zählt zum Tageslimit von 50.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "size_bytes"
                ],
                "additionalProperties": false,
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Der anzuzeigende Dateiname (reiner Text: keine Steuer-, unsichtbaren oder Textrichtungszeichen)."
                  },
                  "size_bytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 2147483648,
                    "description": "Exakte Größe des ZIP in Bytes, höchstens 2 GiB."
                  },
                  "matter_id": {
                    "type": "string",
                    "description": "Optional: die angenommene Aufnahme diesem Mandat hinzufügen."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Import erstellt; laden Sie die Teile hoch.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "uploading"
                      ]
                    },
                    "upload": {
                      "type": "object",
                      "properties": {
                        "upload_id": {
                          "type": "string"
                        },
                        "part_size": {
                          "type": "integer",
                          "example": 33554432
                        },
                        "parts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "part_number": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "format": "uri",
                                "description": "Vorsignierte S3-UploadPart-URL: Senden Sie per PUT genau size_bytes Bytes (die URL signiert die Content-Length), lesen Sie den ETag-Antwort-Header. Kein weiterer Header ist nötig."
                              },
                              "size_bytes": {
                                "type": "integer",
                                "description": "Die exakte Bytelänge dieses Teils: part_size für jeden Teil außer dem letzten, der Rest für den letzten (Teil n beginnt bei (n - 1) x part_size)."
                              }
                            }
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (Body, filename, size_bytes, matter_id, unbekanntes Feld).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: Das Abonnement ist überfällig oder unbezahlt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED (details.reason plan: nur Legal-Tarife; live_credentials_required: ein Testschlüssel, eine Sitzung ohne verknüpfte Abrechnung oder die Sandbox), EMAIL_NOT_VERIFIED, FORBIDDEN (Import eines Platzinhabers des Unternehmens in ein Mandat eines anderen Unternehmens).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_LIMIT (3 Importe in Bearbeitung; details.limit, details.in_progress), MATTER_SEALED, MATTER_ARCHIVED, COMPANY_READ_ONLY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "IMPORT_TOO_LARGE: size_bytes über 2 GiB (details.max_bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: das Limit pro Minute oder das Tageslimit von 50 Importen (details.action import_create, details.daily_limit).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Paketimport"
        ],
        "operationId": "listImports",
        "summary": "Importe auflisten",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Ihre Importe, neueste zuerst. Abgeschlossene Importe werden 30 Tage aufbewahrt (die Aufnahme eines angenommenen Imports bleibt bestehen). Ein Upload nach Ablauf seiner 24 Stunden wird als expired angezeigt.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "next_cursor der vorherigen Seite."
          }
        ],
        "responses": {
          "200": {
            "description": "Eine Seite mit Importen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "imports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Import"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (limit, cursor).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "Import-ID (imp_ gefolgt von 16 bis 24 Buchstaben und Ziffern). Eine fehlerhafte ID gibt 404 IMPORT_NOT_FOUND zurück.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "get": {
        "tags": [
          "Paketimport"
        ],
        "operationId": "getImport",
        "summary": "Import abrufen",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Der Import und, sobald abgeschlossen, sein Ergebnis. Fragen Sie ihn nach POST /imports/{import_id}/complete ab; die Prüfung dauert in der Regel Sekunden, bei einem Paket von 2 GiB bis zu einige Minuten. Mit ?upload=true bei einem noch laufenden Upload ergänzt die Antwort upload: neue vorsignierte URLs (parts) für die Teile, die der Speicher noch nicht hat, und die bereits empfangenen Teile (received: part_number, etag, size_bytes; null, wenn sie nicht aufgelistet werden konnten, dann wird jeder Teil erneut vorsigniert), oder completed: true, wenn der Upload bereits abgeschlossen wurde (senden Sie complete erneut). Verwenden Sie dies, um nach einem Netzwerkabbruch, einer abgelaufenen Teil-URL (sie gelten höchstens 1 Stunde, das Upload-Zeitfenster 24 Stunden) oder einem Neuladen der Seite fortzufahren. ?upload=true kostet eine Einheit des Rate-Limits pro Minute.",
        "responses": {
          "200": {
            "description": "Der Import.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Import"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND (auch der Import eines anderen Kontos).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED (nur mit ?upload=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "name": "upload",
            "in": "query",
            "required": false,
            "description": "true (oder 1): neue Teil-URLs und die empfangenen Teile eines laufenden Uploads ergänzen.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          }
        ]
      },
      "delete": {
        "tags": [
          "Paketimport"
        ],
        "operationId": "cancelImport",
        "summary": "Upload abbrechen",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Bricht einen Import ab, der noch hochgeladen wird: Der Upload wird abgebrochen, die Teile werden gelöscht und der Import entfernt (GET gibt danach 404 zurück).",
        "responses": {
          "200": {
            "description": "Abgebrochen.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: Der Import wird nicht mehr hochgeladen (details.status).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}/complete": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "Import-ID (imp_ gefolgt von 16 bis 24 Buchstaben und Ziffern). Eine fehlerhafte ID gibt 404 IMPORT_NOT_FOUND zurück.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "post": {
        "tags": [
          "Paketimport"
        ],
        "operationId": "completeImport",
        "summary": "Upload abschließen und prüfen",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Schließt den Upload ab und startet die Prüfung. parts ist optional: Senden Sie jede Teilnummer (1 bis N, in Reihenfolge) mit dem ETag, den das PUT des Teils zurückgegeben hat, oder lassen Sie es weg, dann liest der Server die hochgeladenen Teile aus dem Speicher (nützlich in einem Browser, der den ETag-Header nicht lesen kann). Prüft, dass die gespeicherte Größe size_bytes entspricht. Fragen Sie GET /imports/{import_id} ab. Eine Teilliste, die der Speicher ablehnt, oder ein noch nicht hochgeladener Teil (details.missing_parts) ergibt 400, und der Upload bleibt offen: Laden Sie das Fehlende hoch (GET /imports/{import_id}?upload=true liefert neue URLs) und senden Sie die Anfrage erneut. Ein erneutes Senden von complete, nachdem eine Antwort verloren ging, ist sicher: Ein noch laufendes complete gibt 409 zurück, ein abgebrochenes wird nach 2 Minuten übernommen, und ein bereits abgeschlossener Upload geht weiter zur Prüfung. Eine von size_bytes abweichende Größe verwirft den Upload (422 IMPORT_SIZE_MISMATCH; der Import wird abgelehnt, erstellen Sie einen neuen).",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "part_number",
                        "etag"
                      ],
                      "properties": {
                        "part_number": {
                          "type": "integer",
                          "minimum": 1
                        },
                        "etag": {
                          "type": "string",
                          "description": "Der ETag-Antwort-Header des PUT für den Teil, mit oder ohne Anführungszeichen."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Prüfung gestartet.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "verifying"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (Teile nicht in Reihenfolge oder nicht passend zu den hochgeladenen Teilen; oder, ohne parts, ein noch nicht hochgeladener Teil: details.missing_parts).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED, EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: Der Import wird nicht mehr hochgeladen (details.status, zum Beispiel verifying oder expired).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "IMPORT_SIZE_MISMATCH (details.expected, details.actual).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Senden Sie Ihren API-Schlüssel als `Authorization: Bearer ps_live_...` (beim Namen des Schemas wird Groß- und Kleinschreibung nicht unterschieden). Ein fehlender oder fehlerhafter Header gibt 401 MISSING_API_KEY zurück. Ein unbekannter, widerrufener oder abgelaufener Schlüssel oder ein Konto ohne API-Zugang gibt 401 INVALID_API_KEY mit `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"` zurück. 403 ist gültigen Anmeldedaten vorbehalten, die etwas nicht tun dürfen (FORBIDDEN, Tarifbeschränkungen). Ein `ps_live_`-Schlüssel eines Kontos ohne verknüpftes Abrechnungskonto (Stripe-Kunde) läuft als Testschlüssel: Aufnahmen erhalten ein Wasserzeichen und werden nie berechnet, und include_eidas wird mit 403 EIDAS_REQUIRES_LIVE_KEY abgelehnt. Das Gateway speichert die Entscheidung für einen Schlüssel bis zu 60 Sekunden zwischen, daher kann ein widerrufener Schlüssel noch bis zu 60 Sekunden funktionieren. Können die Anmeldedaten wegen eines vorübergehenden Backend-Fehlers nicht geprüft werden, gibt das Gateway stattdessen 503 SERVICE_UNAVAILABLE zurück; diese Antwort wird nicht zwischengespeichert, wiederholen Sie die Anfrage also mit Backoff. Die `WWW-Authenticate`-Challenge wird bei einem 401 vom Gateway (der Prüfung der Anmeldedaten) unverändert zurückgegeben; bei einem 401 von einem Route-Handler liefert das Gateway dieselbe Challenge umbenannt in `x-amzn-Remapped-WWW-Authenticate`."
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Anfrage-ID, auch nützlich bei Supportanfragen. Wird bei erfolgreichen Antworten gesendet; Fehler-Bodys enthalten sie als request_id.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Im aktuellen 60-Sekunden-Zeitfenster erlaubte Anfragen. Älterer Header, beibehalten neben RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Im aktuellen Zeitfenster verbleibende Anfragen. Älterer Header, beibehalten neben RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "ISO-8601-Zeitpunkt, zu dem das aktuelle Zeitfenster endet. Älterer Header, beibehalten neben RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "RetryAfter": {
        "description": "Sekunden, die vor einer Wiederholung zu warten sind.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaLimit": {
        "description": "Im Tarif pro Monat enthaltene Aufnahmen (Live-API-Schlüssel auf Tarifen mit monatlichem Kontingent).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "Verbleibende enthaltene Aufnahmen in diesem Monat.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaUsed": {
        "description": "Abgeschlossene Aufnahmen in diesem Monat.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaCap": {
        "description": "Das monatliche Maximum, das für den Aufrufer gilt (Tarif-Standard, eigene Obergrenze oder Unternehmenslimit).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgLimit": {
        "description": "Nur Unternehmensmitglieder: das unternehmensweite Monatslimit, wenn der Inhaber eines festgelegt hat.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgUsed": {
        "description": "Nur Unternehmensmitglieder: in diesem Monat für das gesamte Unternehmen zugelassene Aufnahmen.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxLimit": {
        "description": "Sandbox (Dashboard-Sitzung ohne Tarif): einmaliges Gesamtkontingent an Testaufnahmen mit Wasserzeichen (50).",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxRemaining": {
        "description": "Sandbox: verbleibendes Kontingent.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxUsed": {
        "description": "Sandbox: verbrauchtes Kontingent.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplay": {
        "description": "\"true\", wenn diese Antwort eine frühere Anfrage mit demselben Idempotency-Key wiedergibt.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "WWWAuthenticate": {
        "description": "RFC-6750-Challenge, die mit jedem 401 gesendet wird. Bei einem 401, das von einem Route-Handler statt vom Gateway zurückgegeben wird, liefert API Gateway sie umbenannt in `x-amzn-Remapped-WWW-Authenticate`. Ein ungültiger, widerrufener oder abgelaufener API-Schlüssel oder ein fehlerhaftes oder widerrufenes Sitzungstoken (INVALID_API_KEY): `Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. Ein fehlender oder fehlerhafter Authorization-Header (MISSING_API_KEY): `Bearer realm=\"ProofSnap API\"`.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer realm=\"ProofSnap API\", error=\"invalid_token\""
      },
      "RateLimitPolicy": {
        "description": "Rate-Limit-Richtlinie nach dem IETF-Entwurf draft-ietf-httpapi-ratelimit-headers (Structured-Field-Form; entwurfsbasiert, kann sich mit dem Entwurf ändern): `\"default\";q=<requests per window>;w=60`.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";q=120;w=60"
      },
      "RateLimit": {
        "description": "Aktueller Rate-Limit-Zustand nach dem IETF-Entwurf draft-ietf-httpapi-ratelimit-headers (entwurfsbasiert): `\"default\";r=<requests left>;t=<seconds until the window resets>`. Wird bei Antworten von Routen mit Rate-Limit (Einzel- und Batch-Aufnahmen, compare, redact, certify-hash) gesendet, nachdem der Limiter gelaufen ist, nicht nur bei 429 (eine Anfrage, die vor dem Limiter abgelehnt wird, zum Beispiel wegen der Authentifizierung, hat ihn nicht).",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";r=117;t=42"
      }
    },
    "parameters": {
      "CaptureId": {
        "name": "capture_id",
        "in": "path",
        "required": true,
        "description": "ID einer Aufnahme, eines Zertifikats, eines Vergleichs, einer Schwärzung oder eines importierten Pakets. IDs beginnen mit cap_, cert_, cmp_, red_ oder imp_. Eine fehlerhafte ID gibt 404 CAPTURE_NOT_FOUND zurück.",
        "schema": {
          "type": "string",
          "example": "cap_a1B2c3D4e5F6g7H8"
        }
      },
      "ScheduleId": {
        "name": "schedule_id",
        "in": "path",
        "required": true,
        "description": "Zeitplan-ID. Ein Zeitplan eines anderen Kontos gibt 404 SCHEDULE_NOT_FOUND zurück.",
        "schema": {
          "type": "string",
          "example": "sch_a1B2c3D4e5F6g7H8"
        }
      },
      "MatterId": {
        "name": "matter_id",
        "in": "path",
        "required": true,
        "description": "Mandats-ID. Ein Mandat, dessen Mitglied Sie nicht sind, gibt 404 MATTER_NOT_FOUND zurück.",
        "schema": {
          "type": "string",
          "example": "mat_a1B2c3D4e5F6g7H8"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optionaler Client-Schlüssel, der eine Wiederholung sicher macht. Nur die ersten 255 Zeichen werden verwendet, und Schlüssel gelten 24 Stunden lang für Ihr Konto. Eine Wiederholung mit demselben Schlüssel und demselben Body gibt den ursprünglichen Antwort-Body mit `Idempotent-Replay: true` zurück (202 für eine einzelne Aufnahme, einen Batch oder einen Vergleich, 201 für eine Schwärzung; `status` wird auf den aktuellen Status der Aufnahme, des Vergleichs oder der Schwärzung sowie jeder Aufnahme eines Batches aktualisiert); es wird keine neue Aufnahme erstellt oder berechnet. Derselbe Schlüssel mit einem anderen Body gibt 422 IDEMPOTENCY_KEY_REUSED zurück. Eine Wiederholung, während die erste Anfrage noch angenommen wird, gibt 409 IDEMPOTENCY_IN_PROGRESS mit der ursprünglichen ID in `error.details.capture_id` zurück (bei einem Batch die batch_id).",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "SubscriptionId": {
        "name": "subscription_id",
        "in": "path",
        "required": true,
        "description": "Webhook-Abonnement-ID. Ein Abonnement eines anderen Kontos gibt 404 WEBHOOK_SUBSCRIPTION_NOT_FOUND zurück.",
        "schema": {
          "type": "string",
          "example": "whs_a1B2c3D4e5F6g7H8"
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "RATE_LIMITED: zu viele Anfragen im aktuellen 60-Sekunden-Zeitfenster. `error.details.retry_after_seconds` wiederholt Retry-After. RateLimit meldet r=0 und die Sekunden bis zum Zurücksetzen des Zeitfensters.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Too many requests. See Retry-After header.",
                "details": {
                  "retry_after_seconds": 42
                }
              },
              "request_id": "..."
            }
          }
        }
      },
      "InternalError": {
        "description": "INTERNAL_ERROR: unerwarteter Serverfehler. Mit exponentiellem Backoff wiederholen.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AuthUnavailable": {
        "description": "SERVICE_UNAVAILABLE: Das Gateway konnte die Anmeldedaten nicht prüfen (vorübergehender Backend-Fehler). Nicht zwischengespeichert: mit Backoff wiederholen.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "SERVICE_UNAVAILABLE",
                "message": "Authentication is temporarily unavailable. Please retry."
              },
              "request_id": "..."
            }
          }
        }
      },
      "EidasOrAuthUnavailable": {
        "description": "EIDAS_UNAVAILABLE: include_eidas wurde angefordert, aber qualifizierte Zeitstempel sind in dieser Bereitstellung nicht konfiguriert; wiederholen Sie die Anfrage ohne include_eidas. SERVICE_UNAVAILABLE: Das Gateway konnte die Anmeldedaten nicht prüfen (vorübergehender Backend-Fehler, nicht zwischengespeichert); mit Backoff wiederholen.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401, zwei Codes. MISSING_API_KEY: Der Authorization-Header fehlt oder ist fehlerhaft (`WWW-Authenticate: Bearer realm=\"ProofSnap API\"`). INVALID_API_KEY: Die Anmeldedaten wurden gesendet, sind aber nicht gültig: ein unbekannter, widerrufener oder abgelaufener API-Schlüssel, ein Konto ohne API-Zugang oder ein ungültiges oder widerrufenes Sitzungstoken (`WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`). Nicht mit denselben Anmeldedaten wiederholen. 403 bedeutet, dass die Anmeldedaten gültig sind, diese Aktion damit aber nicht erlaubt ist (FORBIDDEN, Tarifbeschränkungen).",
        "headers": {
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWWAuthenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "value": {
                  "error": {
                    "code": "MISSING_API_KEY",
                    "message": "Missing or malformed Authorization header. Send Authorization: Bearer <your API key>."
                  },
                  "request_id": "..."
                }
              },
              "invalid": {
                "value": {
                  "error": {
                    "code": "INVALID_API_KEY",
                    "message": "The API key is invalid, revoked or expired, or the account has no API access."
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Jeder Fehler hat diese Form. Fehlerantworten enthalten den Header `X-Request-ID` mit demselben Wert wie `request_id`.\n\nAnfragegröße: Ein Anfrage-Body darf höchstens 6 MB groß sein (das Payload-Limit von Lambda; API Gateway selbst stoppt bei 10 MB). Ein größerer Body wird abgelehnt, bevor er den API-Code erreicht, daher hat diese Antwort nicht diese Form.\n\n| HTTP | Code | Bedeutung |\n| --- | --- | --- |\n| 400 | `MALFORMED_REQUEST` | Der Body ist kein gültiges JSON (Meldung \"Request body is not valid JSON\"), oder die Anfrage ist ungültig: Wenn `details.reason` angibt, welches Feld und warum, wiederholt `message` diesen Grund, sonst lautet sie \"The request is invalid.\" |\n| 400 | `MISSING_FIELD` | Ein Pflichtfeld fehlt. |\n| 400 | `INVALID_URL` | Die URL ist keine gültige http(s)-URL, ist länger als 2048 Zeichen, oder eine webhook_url ist nicht https. |\n| 400 | `INVALID_HASH` | certify-hash: Ein Hash besteht nicht aus 64 Hex-Zeichen. |\n| 400 | `INVALID_TIMESTAMP_TYPE` | options.timestamp_type ist nicht \"bitcoin\". |\n| 400 | `INVALID_VIEWPORT` | options.viewport ist nicht \"desktop\" oder \"mobile\". |\n| 400 | `METADATA_TOO_LARGE` | options.metadata hat mehr als 10 Schlüssel, einen Wert, der kein String ist, oder einen Wert über 500 Zeichen. |\n| 400 | `INVALID_EVIDENCE_PACK_OPTIONS` | options.evidence_pack ist ungültig. |\n| 400 | `INVALID_BATCH` | Batch-urls ist kein Array mit 2 bis 50 gültigen URLs. |\n| 400 | `REDACTION_NO_OPS` | Die Schwärzung hat keine Operationen, oder keine davon hat einen Treffer ergeben. |\n| 400 | `INVALID_CRON` | cron_expr konnte nicht geparst werden. |\n| 400 | `INTERVAL_TOO_SHORT` | Der Zeitplan-Rhythmus liegt unter dem Minimum des Tarifs. |\n| 400 | `RATE_LIMIT_COST_TOO_HIGH` | Die Anfrage benötigt mehr Rate-Limit-Einheiten, als ein Zeitfenster erlaubt; `details.max_per_window`, `details.window_seconds`, `details.requested`. Teilen Sie sie auf. |\n| 400 | `WEBHOOK_REQUIRES_API_KEY` | Eine `webhook_url` wurde aus einer Dashboard-Sitzung gesendet. Webhooks werden mit dem Webhook-Secret eines API-Schlüssels signiert und erfordern daher einen API-Schlüssel. |\n| 401 | `MISSING_API_KEY` | Authorization-Header fehlt oder ist fehlerhaft. `WWW-Authenticate: Bearer realm=\"ProofSnap API\"`. |\n| 401 | `INVALID_API_KEY` | Schlüssel unbekannt, widerrufen oder abgelaufen, kein API-Zugang, oder Sitzungstoken ungültig oder widerrufen. `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. |\n| 402 | `QUOTA_EXCEEDED` | Monatliches Maximum oder Sandbox-Kontingent erreicht (auch im `error` einer aus diesem Grund abgelehnten Hintergrund-Zertifizierung). |\n| 402 | `PAYMENT_REQUIRED` | Das Live-Abonnement ist überfällig oder unbezahlt (auch im `error` einer aus diesem Grund übersprungenen automatischen Aufnahme). |\n| 403 | `FORBIDDEN` | Die Ressource gehört jemand anderem, oder die Aktion ist dem Inhaber vorbehalten. Meldung: \"You do not have access to this resource.\" (details.reason sagt mehr, wo es hilft). |\n| 403 | `EMAIL_NOT_VERIFIED` | Eine Dashboard-Sitzung, deren E-Mail-Adresse nicht bestätigt ist, hat versucht, etwas Dauerhaftes, Kostenpflichtiges oder etwas, das Personen einlädt, zu erstellen (Aufnahmen, Batches, Vergleiche, Schwärzungen, API-Schlüssel, Webhook-Secrets, Checkout, Tarifänderungen, Platzänderungen, certify-hash, Zeitpläne, Unternehmenseinladungen, Mandate und jede Änderung daran, Mandatsmitglieder, Mandaten zugeordnete Aufnahmen, Freigaben und deren Widerruf, Versiegelungen, Exporte, Löschsperren). Lesezugriffe bleiben offen. Meldung: \"Confirm your email address first. Use the link we sent you, or send a new one from the dashboard.\" Ein API-Schlüssel wird ebenso abgelehnt, wenn die E-Mail-Adresse seines Kontos nicht bestätigt ist. |\n| 403 | `EIDAS_REQUIRES_LIVE_KEY` | include_eidas mit einem Testschlüssel gesendet (ein ps_live_-Schlüssel eines Kontos ohne verknüpftes Abrechnungskonto zählt als solcher) oder für einen Vergleich oder eine Schwärzung einer Testaufnahme (`details.reason`). Ein Live-API-Schlüssel oder eine Sitzung, die als Test läuft, weil kein Abrechnungskonto verknüpft ist, erhält `details.reason` \"no_billing_account\". |\n| 403 | `MATTERS_NOT_ENABLED` | Mandate erfordern einen Legal-Tarif. |\n| 403 | `SCHEDULE_LIMIT_REACHED` | Limit aktiver Zeitpläne des Tarifs erreicht (oder der Tarif hat keine Zeitpläne). |\n| 403 | `ON_CHANGE_REQUIRES_PLAN` | Der Tarif erlaubt keine on_change-Überwachung. |\n| 403 | `PLAN_ARCHIVE_ONLY` | Der Tarif Archive only hält Ihre Beweismittel lesbar, herunterladbar und prüfbar; er erstellt keine Aufnahmen, Zeitpläne, Zertifizierungen, Vergleiche oder Schwärzungen. |\n| 403 | `PLAN_NOT_AVAILABLE` | Der Tarif kann von diesem Konto nicht gewählt werden: Archive only wird gekauft, nachdem ein kostenpflichtiges API-Abonnement beendet ist (solange die Beweismittel noch aufbewahrt werden), oder durch einen Tarifwechsel von developer, startup, growth, website_archive oder legal_solo erreicht; das Firmenarchiv nur vom Inhaber eines Unternehmens, dessen Abonnement beendet ist, innerhalb seines Download-Zeitfensters. `details.reason` nennt den Grund. |\n| 403 | `PLAN_API_READ_ONLY` | Tarif Website Archive: Der API-Schlüssel ist schreibgeschützt (auflisten, herunterladen, prüfen, Webhooks). Erstellen Sie Aufnahmen, Zeitpläne und Zertifizierungen im Dashboard, oder verwenden Sie einen API-Tarif. |\n| 403 | `LEGAL_HOLD_OWNER_ONLY` | Nur der Unternehmensinhaber hebt die Löschsperre einer Unternehmensaufnahme auf (ein Platzinhaber darf sie setzen). |\n| 403 | `LEGAL_HOLD_NOT_AVAILABLE` | Das Setzen einer Löschsperre erfordert einen kostenpflichtigen Tarif (`details.reason` \"plan\": die kostenlose Sandbox oder ein Konto, dessen Abonnement beendet ist) und eine Live-Aufnahme (`details.reason` \"test_capture\": eine Aufnahme im Testmodus mit Wasserzeichen). Das Aufheben einer Sperre wird hierfür nie abgelehnt. |\n| 403 | `IMPORT_NOT_ALLOWED` | Der Paketimport erfordert einen Legal-Tarif (`details.reason` \"plan\") und einen Live-API-Schlüssel oder eine Dashboard-Sitzung mit verknüpfter Abrechnung (`details.reason` \"live_credentials_required\"). |\n| 404 | `CAPTURE_NOT_FOUND` | Die Aufnahme existiert nicht, wurde gelöscht, oder die ID ist fehlerhaft. |\n| 404 | `MATTER_NOT_FOUND` | Das Mandat existiert nicht, oder Sie sind kein Mitglied. |\n| 404 | `IMPORT_NOT_FOUND` | Der Import existiert nicht, gehört einem anderen Konto, oder die ID ist fehlerhaft. |\n| 404 | `WEBHOOK_SUBSCRIPTION_NOT_FOUND` | Das Webhook-Abonnement existiert nicht, wurde gelöscht oder gehört einem anderen Konto. |\n| 404 | `SCHEDULE_NOT_FOUND` | Der Zeitplan existiert nicht oder gehört einem anderen Konto. |\n| 404 | `NOT_FOUND` | Andere Ressource nicht gefunden (Hash-Format des Registers, Freigabe-Token, Export-Job, Mitglied). |\n| 404 | `EXPORT_JOB_NOT_FOUND` | Ein Aufnahme-Export-Job existiert nicht, gehört einem anderen Konto, ist abgelaufen oder wurde mit einem Testschlüssel angefragt. |\n| 405 | `METHOD_NOT_ALLOWED` | Die HTTP-Methode ist für diese Route nicht erlaubt. |\n| 409 | `CAPTURE_PROCESSING` | Die Aufnahme wird noch verarbeitet und kann noch nicht gelöscht werden (bzw. ihre Löschsperre noch nicht geändert werden). |\n| 409 | `CAPTURE_LEGAL_HOLD` | Die Aufnahme steht unter Löschsperre und kann nicht gelöscht werden. |\n| 409 | `CAPTURE_IN_TRASH` | Löschsperre: Die Aufnahme liegt im Papierkorb; stellen Sie sie zuerst wieder her. |\n| 409 | `CAPTURE_HOLD_GRACE` | Endgültige Löschung innerhalb der Karenzzeit nach Aufhebung der Löschsperre der Aufnahme; `details.until`. Das Verschieben in den Papierkorb ist erlaubt. |\n| 409 | `LEGAL_HOLD_MATTER` | Die Sperre der Aufnahme wurde durch die Löschsperre ihres Mandats gesetzt, die noch aktiv ist: Heben Sie sie am Mandat auf (`details.matter_id` für einen Leser des Mandats). |\n| 409 | `LEGAL_HOLD_SOURCE` | Die Sperre einer Schwärzung oder eines Vergleichs folgt einer Aufnahme, aus der sie bzw. er erstellt wurde (bei einem Vergleich einer der beiden verglichenen Aufnahmen), die noch unter Löschsperre steht (auf dieser Aufnahme oder über ihr Mandat gesetzt): Das Paket bleibt gesperrt, bis diese Sperre aufgehoben wird (`details.source_capture_id`). |\n| 409 | `COMPANY_READ_ONLY` | Das Abonnement des Unternehmens ist beendet (oder es wird als Firmenarchiv geführt): Seine Mandate können nur noch geöffnet und exportiert und ihre Freigabelinks widerrufen werden, und aus seinen Aufnahmen wird nichts Neues abgeleitet. |\n| 409 | `WEBHOOK_SUBSCRIPTION_LIMIT` | Limit aktiver Webhook-Abonnements des Tarifs erreicht (25 auf API- und Legal-Tarifen, 10 auf Website Archive, 0 auf Archive only und Sandbox); `details.limit`, `details.active`. |\n| 409 | `WEBHOOK_SECRET_ROTATION_PENDING` | Das vorherige Webhook-Secret befindet sich noch in seiner 24-stündigen Überlappung; `details.previous_secret_expires_at`. |\n| 409 | `IDEMPOTENCY_IN_PROGRESS` | Die erste Anfrage mit diesem Idempotency-Key wird noch verarbeitet. |\n| 409 | `COMPARE_NOT_READY` | Beide Aufnahmen müssen vor dem Vergleichen abgeschlossen sein. |\n| 409 | `REDACTION_NOT_READY` | Die Quellaufnahme muss vor dem Schwärzen abgeschlossen sein. |\n| 409 | `MATTER_SEALED` | Das Mandat (oder das Mandat der Aufnahme) ist versiegelt und kann nicht geändert werden. |\n| 409 | `MATTER_SEALING` | Eine Versiegelung des Mandats läuft (höchstens 60 s): währenddessen keine Zuordnung von Aufnahmen, keine Umbenennung, keine Mitgliederänderung, kein Verschieben einer verknüpften Aufnahme in den Papierkorb und keine zweite Versiegelung. `Retry-After`. |\n| 409 | `MATTER_ARCHIVED` | Das Mandat ist archiviert: keine neuen Aufnahmen, Umbenennungen, neuen Mitglieder, Freigaben oder Exporte; öffnen Sie es zuerst wieder (PATCH status \"open\"). Das Entfernen einer Aufnahme oder eines Mitglieds funktioniert weiterhin. |\n| 409 | `MATTER_CHANGED` | Das Beweiskettenprotokoll hat sich während der Versiegelung geändert; es wurde nichts versiegelt, wiederholen Sie die Anfrage. |\n| 409 | `MATTER_LINKED` | Löschen einer Aufnahme: Die Aufnahme ist ein Beweisstück eines Mandats, dem der Aufrufer nicht mehr angehört. Der Inhaber des Mandats entfernt sie zuerst aus dem Mandat. |\n| 409 | `MATTER_HAS_TRASHED_CAPTURES` | Versiegelung eines Mandats: Eine Aufnahme des Mandats liegt im Papierkorb. Stellen Sie sie wieder her oder entfernen Sie sie aus dem Mandat, und versiegeln Sie dann erneut. |\n| 409 | `MEMBER_HAS_INDIVIDUAL_SUBSCRIPTION` | Unternehmenseinladung: Das eigene API-Abonnement der eingeladenen Person verlängert sich noch. Die eingeladene Person kündigt es, danach lädt der Inhaber sie erneut ein. |\n| 409 | `IMPORT_LIMIT` | Es sind bereits 3 Importe in Bearbeitung (Upload oder Prüfung); `details.limit`, `details.in_progress`. |\n| 409 | `IMPORT_STATE` | Der Import ist nicht in einem Zustand, der dies erlaubt (complete oder Abbruch eines Imports, der nicht mehr hochgeladen wird); `details.status`. |\n| 409 | `EXPORT_JOB_RUNNING` | Ein Export Ihrer Aufnahmen wird noch vorbereitet; `details.job_id` und `details.poll_path` nennen ihn. Starten Sie einen neuen, wenn er beendet ist. |\n| 413 | `IMPORT_TOO_LARGE` | Das Paket ist größer als 2 GiB; `details.max_bytes`. |\n| 413 | `VERIFY_PACKAGE_TOO_LARGE` | POST /captures/{capture_id}/verify: Das gespeicherte Paket ist größer als 256 MiB; prüfen Sie es offline mit den mitgelieferten Skripten. `details.size_bytes`, `details.limit_bytes`. |\n| 422 | `IDEMPOTENCY_KEY_REUSED` | Idempotency-Key mit einem anderen Body oder einer anderen Schlüsselumgebung (live oder test) wiederverwendet. |\n| 422 | `SSRF_BLOCKED` | Die URL zielt auf eine private, Loopback-, Link-Local- oder anderweitig gesperrte Adresse. |\n| 422 | `TARGET_UNREACHABLE` | Der Hostname der URL hat keine DNS-Einträge oder konnte nicht aufgelöst werden. |\n| 422 | `REDACTION_SCREENSHOT_UNREADABLE` | Der Screenshot konnte für die Schwärzung von Bereichen nicht dekodiert werden. |\n| 422 | `REDACTION_LEAK_DETECTED` | Ein geschwärzter Begriff hat die Schwärzung außerhalb der Aufnahme-URL überstanden; es wurde nichts ausgestellt. `details.files`, `details.rule_indexes`, `details.metadata_fields`, `details.in_capture_url`. |\n| 422 | `REDACTION_SOURCE_UNSUPPORTED` | Nur Aufnahmen von Webseiten können geschwärzt werden. `details.source` oder `details.manifest_type`. |\n| 422 | `REDACTION_SOURCE_INTEGRITY_FAILED` | Eine Datei der Quellaufnahme entspricht nicht dem SHA-256, den ihr signiertes Manifest aufführt, oder fehlt; es wurde nichts ausgestellt. `details.mismatched`, `details.missing`. |\n| 422 | `COMPARE_PROFILE_MISMATCH` | POST /compare: Die beiden Aufnahmen wurden mit einem anderen Geräteprofil oder anderen Aufnahmeeinstellungen erstellt; `details.differing`, `details.before`, `details.after`. Senden Sie allow_profile_mismatch: true, um trotzdem zu vergleichen (das Text- und Bildergebnis ist dann unbestimmt). |\n| 422 | `COMPARE_URL_MISMATCH` | POST /compare: Die beiden Aufnahmen zeigen verschiedene Seiten; `details.before`, `details.after` (url und final_url als Origin und Pfad). Senden Sie allow_url_mismatch: true, um trotzdem zu vergleichen. |\n| 422 | `RULE902_NOT_AVAILABLE_FOR_MISMATCH` | POST /compare: include_rule_902 für unterschiedlich gerenderte Aufnahmen (allow_profile_mismatch); `details.differing`. |\n| 422 | `COMPARE_SOURCE_INTEGRITY_FAILED` | Eine Datei einer Quellaufnahme von POST /compare entspricht nicht dem SHA-256, den ihr signiertes Manifest aufführt, oder fehlt; es wurde nichts verglichen oder berechnet. `details.capture_id`, `details.mismatched`, `details.missing`, `details.sources[]`. |\n| 422 | `REDACTION_PATTERN_TOO_SLOW` | Ein regulärer Ausdruck in text_redactions lief auf dieser Seite zu lange (katastrophales Backtracking); es wurde nichts ausgestellt oder berechnet. `details.rule_index`. Vereinfachen Sie das Muster. |\n| 422 | `IMPORT_SIZE_MISMATCH` | Das hochgeladene Paket hat nicht die angekündigte size_bytes; der Upload wurde verworfen. `details.expected`, `details.actual`. |\n| 422 | `IMPORT_NOT_SUPPORTED` | Vergleich oder Schwärzung eines importierten Beweispakets. |\n| 429 | `RATE_LIMITED` | Zu viele Anfragen im aktuellen Zeitfenster, oder ein Tageslimit für das Erstellen von Mandaten (200), Versiegelungen (20), Exporten (50), Mitgliedereinladungen (50) oder Paketimporten (50, `import_create`) wurde erreicht (`details.daily_limit`, `details.action`). `Retry-After` gibt an, wann Sie es erneut versuchen können. |\n| 429 | `CONCURRENT_CAPTURE_LIMIT` | Zu viele Aufnahmen des Kontos laufen gleichzeitig (developer 10, startup 25, growth 100, legal_solo und legal_seats 25, custom 200, website_archive 25, sandbox 5). Ein Batch startet so viele URLs, wie passen, und führt den Rest in `not_admitted` auf; ohne freien Slot wird die gesamte Anfrage abgelehnt. `details.limit`, `details.in_flight`, `details.requested`, `Retry-After`. |\n| 429 | `TEST_MODE_DAILY_LIMIT` | Arbeit im Testmodus (ein Testschlüssel oder eine Sitzung ohne verknüpfte Abrechnung; mit Wasserzeichen, nicht berechnet) ist auf 200 Einheiten pro Konto und UTC-Tag begrenzt (ein Batch zählt seine URLs). `details.used`, `details.limit`, `details.reset_at`, `Retry-After`. |\n| 429 | `IMPORT_DAILY_BYTES_LIMIT` | Paketimport: Das Importvolumen des Kontos für den UTC-Tag (5 GiB angekündigter Größen) ist erreicht. `details.period`, `details.limit_bytes`, `details.used_bytes`, `details.requested_bytes`, `details.reset_at`. |\n| 429 | `IMPORT_MONTHLY_BYTES_LIMIT` | Paketimport: Das Importvolumen des Kontos für den UTC-Monat (20 GiB) ist erreicht. Dieselben Details. |\n| 500 | `INTERNAL_ERROR` | Unerwarteter Fehler. Mit Backoff wiederholen. |\n| 500 | `CAPTURE_FAILED` | Die Aufnahme konnte nicht gestartet oder (im `error` einer fehlgeschlagenen Aufnahme) nicht abgeschlossen werden. |\n| 500 | `CAPTURE_TIME_BUDGET_EXCEEDED` | Im `error` einer fehlgeschlagenen Aufnahme oder bei einer synchronen Schwärzung: Der Durchlauf hat sein Zeitbudget aufgebraucht, und es wurde kein Paket ausgestellt oder berechnet. |\n| 500 | `CAPTURE_SCREENSHOT_TIMEOUT` | Im `error` einer fehlgeschlagenen Aufnahme: Der Browser hat das Rendern des Screenshots nicht abgeschlossen: Jede Methode, ihn zu erstellen (eine Aufnahme am Stück, kleinere Streifen, der browsereigene Screenshot), lief in eine Zeitüberschreitung, ebenso der eine Wiederholungsversuch in einem neu gestarteten Browser, sofern das Zeitbudget dafür Raum ließ. Es wurde nichts berechnet; versuchen Sie es erneut. |\n| 500 | `CERTIFY_FAILED` | Im `error` einer fehlgeschlagenen Zertifizierung: Die Zertifizierung konnte nicht abgeschlossen werden. |\n| 500 | `REDACTION_FAILED` | Im `error` einer fehlgeschlagenen Schwärzung: Die Schwärzung konnte nicht abgeschlossen werden. Eine aus einem bekannten Grund abgelehnte Schwärzung trägt stattdessen den Code dieses Grundes (zum Beispiel REDACTION_LEAK_DETECTED). |\n| 500 | `TIMESTAMP_SERVICE_UNAVAILABLE` | Versiegelung eines Mandats: Der OpenTimestamps-Anker ist fehlgeschlagen; nichts wurde geändert, wiederholen Sie die Anfrage. |\n| 500 | `CONFIGURATION_ERROR` | Ein serverseitiger Schritt ist fehlgeschlagen oder nicht konfiguriert (`details.reason`). |\n| 503 | `SERVICE_UNAVAILABLE` | Die Authentifizierung ist vorübergehend nicht verfügbar (die Anmeldedaten konnten nicht geprüft werden). Nicht zwischengespeichert; wiederholen Sie die Anfrage. |\n| 503 | `EIDAS_UNAVAILABLE` | include_eidas angefordert, aber eIDAS-Zeitstempel sind nicht konfiguriert. |\n| 503 | `EIDAS_SEAL_RETRY` | Versiegelung eines Mandats: Der eIDAS-Zeitstempel ist nicht rechtzeitig eingetroffen; es wurde nichts versiegelt. Wiederholen Sie die Anfrage nach `Retry-After`; die Wiederholung verwendet den bereits ausgestellten Zeitstempel. |\n| 503 | `LIMIT_CHECK_UNAVAILABLE` | Versiegelung eines Mandats: Ihr Tageslimit konnte nicht geprüft werden; es wurde nichts getan. Wiederholen Sie die Anfrage. |\n| 504 | `UPSTREAM_TIMEOUT` | Ein synchroner Verarbeitungsschritt (certify-hash, Mandatsexport) hat nicht rechtzeitig geantwortet. Er kann dennoch abgeschlossen werden: Fragen Sie ab, was `details` nennt (`certify_id`, `poll_path`), oder wiederholen Sie die Anfrage. |\n| 404 | `ORG_INVITE_NOT_FOUND` | Eine Unternehmenseinladung, die unbekannt ist, an jemand anderen gerichtet ist, nicht mehr aussteht oder deren Unternehmen nicht mehr aktiv ist (details.reason `company_inactive`). |\n| 409 | `ORG_INVITE_NO_SEATS` | Annahme einer Unternehmenseinladung: Jeder Platz des Unternehmens ist belegt; die Einladung bleibt offen. |\n| 409 | `ORG_INVITE_IN_OTHER_ORG` | Annahme einer Unternehmenseinladung: Der Aufrufer belegt einen Platz eines anderen Unternehmens; die Einladung bleibt offen. |\n| 409 | `ORG_INVITE_HAS_SUBSCRIPTION` | Annahme einer Unternehmenseinladung: Das eigene API-Abonnement des Aufrufers verlängert sich noch; die Einladung bleibt offen. |\n| 409 | `ORG_OWNER_CANNOT_LEAVE` | Der Unternehmensinhaber hat versucht, sein eigenes Unternehmen zu verlassen. |\n| 410 | `ORG_INVITE_EXPIRED` | Annahme einer Unternehmenseinladung, die älter als 7 Tage ist. |",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Maschinenlesbarer Fehlercode (UPPER_SNAKE_CASE)."
              },
              "message": {
                "type": "string",
                "description": "Menschenlesbare Meldung."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Optionaler zusätzlicher Kontext, zum Beispiel `reason`, `field` oder `retry_after_seconds`."
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "Anfrage-ID."
          }
        },
        "example": {
          "error": {
            "code": "INVALID_URL",
            "message": "URL is not a valid http(s) URL",
            "details": {
              "reason": "Only http and https schemes are allowed"
            }
          },
          "request_id": "..."
        }
      },
      "CaptureOptions": {
        "type": "object",
        "description": "Aufnahmeoptionen. Jedes Feld ist optional.",
        "properties": {
          "scroll_full_page": {
            "type": "boolean",
            "description": "Die gesamte scrollbare Seite aufnehmen. false nimmt nur den Viewport auf.",
            "default": true
          },
          "expand_collapsed": {
            "type": "boolean",
            "description": "Eingeklappte Abschnitte vor dem Screenshot öffnen: geschlossene <details>-Elemente, Akkordeon- und Aufklapp-Schaltflächen (aria-expanded mit aria-controls) sowie Textsteuerelemente wie \"see more\" / \"show replies\". Abgesichert: nichts innerhalb von Navigation, Kopfbereichen, Menüs oder Dialogen, keine Links zu einer anderen URL, kein Absenden von Formularen, keine zustandsändernden Beschriftungen (Gefällt mir, Folgen, Kaufen, Löschen, ...), jedes Steuerelement höchstens einmal, und ein Steuerelement, das versucht, die Seite zu verlassen, beendet das Aufklappen, und die ursprüngliche URL wird erneut geladen. Das Ergebnis wird pro Steuerelement in forensic_log.json erfasst (page_modifications.expand_collapsed: gefunden, geöffnet, geschlossen gelassen, Gründe für das Überspringen, gestartete Anfragen, Seitenhöhe vorher und nachher) mit einer Zusammenfassung in metadata.json (expand_collapsed: requested, opened_total, left_closed_total); evidence.pdf und die Zertifikate geben nur an, was tatsächlich geöffnet wurde.",
            "default": true
          },
          "viewport": {
            "type": "string",
            "description": "desktop ist 1920x1080 bei Pixelverhältnis 1; mobile ist 390x844 bei Pixelverhältnis 3 mit einem User-Agent von Mobile Safari. Andere Werte geben 400 INVALID_VIEWPORT zurück.",
            "enum": [
              "desktop",
              "mobile"
            ],
            "default": "desktop"
          },
          "viewport_width": {
            "type": "number",
            "minimum": 320,
            "maximum": 3840,
            "description": "Benutzerdefinierte Viewport-Breite in CSS-Pixeln. Muss zusammen mit viewport_height gesendet werden, sonst 400 MALFORMED_REQUEST."
          },
          "viewport_height": {
            "type": "number",
            "minimum": 240,
            "maximum": 4320,
            "description": "Benutzerdefinierte Viewport-Höhe in CSS-Pixeln. Muss zusammen mit viewport_width gesendet werden."
          },
          "device_scale_factor": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "description": "Gerätepixelverhältnis des Screenshots."
          },
          "wait_for_selector": {
            "type": "string",
            "description": "Vor der Aufnahme bis zu 10 Sekunden auf diesen CSS-Selektor warten. Erscheint er nicht, wird die Aufnahme ohne Fehler fortgesetzt. Erfasst in forensic_log.json page_modifications.",
            "maxLength": 200
          },
          "wait_ms": {
            "type": "number",
            "minimum": 0,
            "maximum": 15000,
            "description": "Zusätzliche Wartezeit in Millisekunden, nachdem die Seite geladen wurde."
          },
          "screenshot_format": {
            "type": "string",
            "description": "jpeg liefert screenshot.jpeg; png liefert einen verlustfreien screenshot.png. C2PA Content Credentials werden nur JPEG-Screenshots hinzugefügt.",
            "enum": [
              "jpeg",
              "png"
            ],
            "default": "jpeg"
          },
          "url_stamp": {
            "type": "boolean",
            "description": "Über dem Seitenbild einen Kopfbalken einfügen, der die aufgenommene URL, die UTC-Aufnahmezeit (der Moment, in dem der Browser den Screenshot erzeugt hat; bei einer langen, in Streifen aufgenommenen Seite der Moment, in dem der letzte Streifen erzeugt wurde) und die Beweis-ID zeigt, gekennzeichnet als Software-Anmerkung. Der Balken wird oberhalb des Bildes angefügt, daher wird kein Seitenpixel verdeckt. chain_of_custody.json erfasst screenshot_url_header.raw_screenshot_sha256 mit raw_screenshot_source: bei einer in einem Stück aufgenommenen Seite (browser_image, browser_png, browser_jpeg_joined, browser_jpeg_reencoded) den SHA-256 des vom Browser erzeugten Seitenbilds vor dem Balken; bei einer langen, in Streifen aufgenommenen und zusammengefügten Seite (joined_bands, joined_bands_lossless, joined_bands_reencoded) den SHA-256 des zusammengefügten Bildes, wie ProofSnap es mit dem Balken geschrieben hat, vor etwaigen C2PA Content Credentials. Kann der Balken nicht hinzugefügt werden (mit ihm würde das Bild die 65.535 px überschreiten, die ein JPEG fassen kann, das Zeichnen ist fehlgeschlagen oder, nur auf dem Ausweich-Screenshot-Pfad, das Seitenbild hat mehr als 32.000.000 Pixel), wird der Screenshot ohne ihn geliefert: metadata.json erfasst dies in screenshot.url_stamp_skipped und chain_of_custody.json in screenshot_url_header (applied false, mit dem Grund).",
            "default": false
          },
          "network_log": {
            "type": "boolean",
            "description": "network.har (HAR mit Anfrage- und Antwort-Headern sowie Timing, ohne Bodys) und network.original.har einschließen.",
            "default": true
          },
          "warc": {
            "type": "boolean",
            "description": "capture.warc (ISO 28500) mit den HTTP-Transaktionen des Seitenaufrufs einschließen. Die Antwort-Bodys werden innerhalb eines Zeitlimits gelesen (höchstens 30 s, und nur, solange der optionale Teil des Zeitbudgets der Aufnahme es zulässt, nach der für eIDAS, C2PA und den abschließenden Uhrzeitabgleich reservierten Zeit); ein durch dieses Limit abgeschnittener Body wird leer mit WARC-Truncated: time gespeichert, eine nicht erreichte Antwort wird weggelassen, und warc_captured in chain_of_custody.json (sowie warc in forensic_log.json) erfasst truncated, truncation_reason, response_pairs, records_total und die Zählwerte.",
            "default": false
          },
          "external_archive": {
            "type": "boolean",
            "description": "Den Dienst Save Page Now des Internet Archive bitten, die URL zu archivieren (das Internet Archive hält diese Kopie öffentlich vor). Das Ergebnis wird danach klassifiziert, wessen Anfrage den Snapshot erzeugt hat und wann er relativ zur Anfrage entstanden ist: created (für diese Anfrage erstellt), recent_existing (das Internet Archive antwortete mit einem früheren Snapshot), created_after_request (ein neuerer Snapshot existiert, aber die Anfrage wurde nicht bestätigt), existing (nur ein älterer Snapshot existiert), pending, rate_limited, failed (das Internet Archive hat die Anfrage abgelehnt), unconfirmed (keine Antwort hat sie bestätigt oder abgelehnt, und kein Snapshot ist bekannt), unavailable, skipped, skipped_sensitive oder skipped_query. URLs, die nach Anmeldedaten aussehen oder tokenartige Parameter, Parameternamen oder Pfadsegmente enthalten, werden nie gesendet. Eine URL mit Query-String wird nur gesendet, wenn diese Option ausdrücklich auf true gesetzt ist; mit dem Standardwert wird sie übersprungen (skipped_query). Das Ergebnis wird in forensic_log.json (external_archive, mit snapshot_offset_seconds gegenüber der Aufnahme) und chain_of_custody.json erfasst und von GET /captures/{capture_id} als external_archive zurückgegeben. Setzen Sie false für private URLs. Die Anfrage wird gesendet, sobald die Seite geladen ist, parallel zum Rest der Aufnahme. Ein Fehlschlag lässt die Aufnahme nicht fehlschlagen.",
            "default": true
          },
          "dismiss_consent": {
            "type": "boolean",
            "description": "Vor dem Laden der Seite Einwilligungs-Cookies vom Typ „alle akzeptieren“ setzen und versuchen, Cookie-Banner zu schließen. Dies verändert die Seite, daher ist es standardmäßig deaktiviert; was getan wurde, wird in forensic_log.json consent_dismissal erfasst.",
            "default": false
          },
          "dismiss_popups": {
            "type": "boolean",
            "description": "Newsletter-, Werbe- und ähnliche Overlay-Pop-ups über ihr eigenes Schließen-Steuerelement schließen. Die Anzahl der geschlossenen Pop-ups und die verwendeten Steuerelemente werden in forensic_log.json page_modifications erfasst. Jedes geschlossene Pop-up wird in forensic_log.json page_modifications erfasst (popups_dismissed, popups_dismissed_controls); evidence.pdf gibt an, wie viele geschlossen wurden.",
            "default": false
          },
          "dark_mode": {
            "type": "boolean",
            "description": "Mit dem dunklen Farbschema rendern. Verändert das Erscheinungsbild der Seite; erfasst in forensic_log.json page_modifications. Offengelegt in evidence.pdf und in forensic_log.json page_modifications.",
            "default": false
          },
          "block_ads": {
            "type": "boolean",
            "description": "Anfragen an bekannte Werbe- und Tracker-Hosts blockieren. Lässt Inhalte weg; erfasst in forensic_log.json page_modifications.",
            "default": false
          },
          "block_resources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "image",
                "font",
                "media",
                "stylesheet"
              ]
            },
            "description": "Zu blockierende Ressourcentypen. Lässt Inhalte weg; erfasst in forensic_log.json page_modifications."
          },
          "inject_css": {
            "type": "string",
            "description": "Vor der Aufnahme zur Seite hinzugefügtes CSS. Verändert die Seite; wörtlich gespeichert in forensic_log.json page_modifications. Immer wörtlich gespeichert in forensic_log.json page_modifications (injected_css) mit injected_css_status `applied` oder `failed` und injected_css_error. evidence.pdf gibt an, dass benutzerdefiniertes CSS eingefügt wurde; den Code druckt es nicht ab.",
            "maxLength": 10000
          },
          "inject_js": {
            "type": "string",
            "description": "Vor der Aufnahme in der Seite ausgeführtes JavaScript. Verändert die Seite. Immer wörtlich gespeichert in forensic_log.json page_modifications (injected_js) mit injected_js_status und injected_js_error: `applied` (es lief bis zum Ende), `invalid` (es ließ sich nicht kompilieren, zum Beispiel wegen eines Syntaxfehlers, lief also nie und hat die Seite nicht verändert), `failed` (es hat während der Ausführung einen Fehler geworfen und kann die Seite daher vor dem Abbruch verändert haben) oder `timed_out` (die Aufnahme hat nach 5 Sekunden aufgehört, darauf zu warten; das Skript kann weitergelaufen sein und die Seite weiter verändert haben). evidence.pdf gibt das Ergebnis an und verweist auf forensic_log.json, und das Herkunftszertifikat führt eingefügten Code unter den Seitenänderungen auf, sofern er nicht `invalid` war; keines von beiden druckt den Code ab.",
            "maxLength": 10000
          },
          "locale": {
            "type": "string",
            "description": "Zu emulierende Browser-Locale, zum Beispiel de oder de-DE (setzt die Browsersprache und Accept-Language). Nur Emulation: Es wird kein Proxy verwendet, daher spiegeln Inhalte, die von der IP des Besuchers abhängen, weiterhin die Region des Aufnahmeservers wider. Erfasst in metadata.json geo.",
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "timezone": {
            "type": "string",
            "description": "Zu emulierende Browser-Zeitzone (IANA-Name, zum Beispiel Europe/Berlin). Nur Emulation."
          },
          "language": {
            "type": "string",
            "description": "Sprache der menschenlesbaren PDFs. Einer der Werte: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Groß- und Kleinschreibung, Bindestrich und Region werden toleriert („DE“, „fr-FR“ und „PT-BR“ werden akzeptiert); ein bloßes „pt“ oder „zh“ wird abgelehnt. Ein nicht unterstützter Wert liefert 400 MALFORMED_REQUEST.",
            "default": "en",
            "example": "de"
          },
          "timestamp_type": {
            "type": "string",
            "description": "Nur \"bitcoin\" wird akzeptiert (der OpenTimestamps-Anker wird immer versucht). Jeder andere Wert gibt 400 INVALID_TIMESTAMP_TYPE zurück. Verwenden Sie include_eidas für einen qualifizierten Zeitstempel.",
            "enum": [
              "bitcoin"
            ],
            "default": "bitcoin"
          },
          "include_eidas": {
            "type": "boolean",
            "description": "Zusätzlich einen qualifizierten eIDAS-Zeitstempel (RFC 3161) über den SHA-256 von manifest.json anfordern, geliefert als manifest.json.tsr (plus eidas_validation.json, wenn Validierungsdaten zurückgegeben werden). Nur Live-API-Schlüssel: Ein Testschlüssel gibt 403 EIDAS_REQUIRES_LIVE_KEY zurück. Ist in der Bereitstellung kein eIDAS-Dienst konfiguriert, wird die Anfrage mit 503 EIDAS_UNAVAILABLE abgelehnt, bevor etwas berechnet wird. Bevor ein Token verwendet wird, prüft ProofSnap es (Signatur, der enthaltene Hash, Zertifikatskette bis zur Root CA Disig QCA4, der Status des TSA-Zertifikats) und erfasst diese Prüfung in eidas_validation.json (verification). Schlägt die Zeitstempelanfrage während der Aufnahme fehl oder besteht das Token diese Prüfung nicht, wird das Paket ohne Zeitstempel nur mit dem OpenTimestamps-Anker geliefert, der Stempel wird nicht berechnet, und timestamps.eidas (Aufnahmezeile, GET /captures/{capture_id}, completed-Webhook) meldet status not_obtained mit dem Grund (token_refused, unavailable oder time_budget). Ab dem Screenshot lassen die optionalen Schritte der Aufnahme (die WARC-Bodys, die Archiv- und WHOIS-Antworten, C2PA-Signierung, der zweite Uhrzeitabgleich) Zeit für den Stempel und werden zuerst gekürzt. Abrechnung pro Stempel.",
            "default": false
          },
          "include_rule_902": {
            "type": "boolean",
            "description": "rule_902_certification.pdf hinzufügen: eine unsignierte Zertifizierungsvorlage nach [FRE 902(13)/(14)](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-app-federalru-dup2-rule902.htm) und [28 U.S.C. 1746](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-partV-chap115-sec1746.htm), die eine Person ausfüllt und unterschreibt. Ihr SHA-256 wird in manifest.json aufgeführt. Sie wird auch hinzugefügt, wenn evidence_pack.custodian einen nicht leeren Wert hat. Schlägt ihre Erzeugung fehl, wird die Aufnahme ohne sie abgeschlossen. Derzeit deaktiviert: Die Zertifizierungsvorlage ist vorübergehend abgeschaltet, daher wird dieses Feld akzeptiert und ignoriert (es wird keine rule_902_certification.pdf erzeugt oder im Paket aufgeführt), und die Antwort enthält die Warnung RULE902_DISABLED.",
            "default": false
          },
          "case_reference": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Ihre Mandats- oder Fallbezeichnung. Leerraum wird zusammengefasst. Folgt der Regel für reinen Text (siehe PlainText). Erfasst in metadata.json, forensic_log.json und chain_of_custody.json und auf evidence.pdf gedruckt.",
            "maxLength": 200
          },
          "case_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Die Fallkennung Ihres Systems. Wird wie case_reference erfasst; folgt der Regel für reinen Text (siehe PlainText).",
            "maxLength": 200
          },
          "case_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Freitext-Notiz. Folgt der Regel für reinen Text (siehe PlainText). Erfasst in metadata.json, forensic_log.json und chain_of_custody.json (legal_context.note).",
            "maxLength": 500
          },
          "webhook_url": {
            "type": "string",
            "description": "https-URL, die capture.completed oder capture.failed empfängt. Siehe Webhooks. Die URL muss https sein und darf nicht auf eine private, Loopback-, Link-Local-, Carrier-Grade-NAT- oder Cloud-Metadaten-Adresse verweisen (dieselben Regeln wie für eine Aufnahme-URL; sonst 422 SSRF_BLOCKED). Ein leerer oder nur aus Leerraum bestehender Wert bedeutet kein Webhook. Erfordert einen API-Schlüssel: Aus einer Dashboard-Sitzung gesendet, wird er mit 400 WEBHOOK_REQUIRES_API_KEY abgelehnt, weil Webhooks mit dem Webhook-Secret des Schlüssels signiert werden.",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "maxProperties": 10,
            "description": "Bis zu 10 String-Tags (Werte bis 500 Zeichen), mit der Aufnahme zurückgegeben. Verstöße geben 400 METADATA_TOO_LARGE zurück."
          },
          "evidence_pack": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "false lässt die Zertifizierungsseite aus evidence.pdf weg.",
                "default": true
              },
              "jurisdiction": {
                "type": "string",
                "description": "Erfasst in forensic_log.json options. Hat keine weitere Wirkung.",
                "enum": [
                  "US-Federal",
                  "EU-eIDAS"
                ],
                "default": "US-Federal"
              },
              "custodian": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Akzeptiert; nicht in der Vorlage gedruckt, deren Angaben zum Erklärenden für die unterzeichnende Person leer bleiben.",
                    "maxLength": 200
                  },
                  "law_firm": {
                    "type": "string",
                    "description": "Akzeptiert; nicht in der Vorlage gedruckt, deren Angaben zum Erklärenden für die unterzeichnende Person leer bleiben.",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "description": "Akzeptiert; in der Vorlage nicht verwendet.",
                    "maxLength": 200
                  },
                  "case_caption": {
                    "type": "string",
                    "description": "Akzeptiert; in der Vorlage nicht verwendet.",
                    "maxLength": 200
                  }
                },
                "additionalProperties": false,
                "description": "Jeder Wert folgt der Regel für reinen Text (siehe PlainText); sonst 400 INVALID_EVIDENCE_PACK_OPTIONS."
              }
            },
            "description": "Ungültige Werte geben 400 INVALID_EVIDENCE_PACK_OPTIONS zurück."
          }
        }
      },
      "CaptureRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Aufzunehmende Seite. Nur http oder https, höchstens 2048 Zeichen. Private, Loopback-, Link-Local- und Metadaten-Adressen werden mit 422 SSRF_BLOCKED abgelehnt, und ein Hostname ohne DNS-Einträge gibt 422 TARGET_UNREACHABLE zurück.",
            "format": "uri",
            "maxLength": 2048
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "$ref": "#/components/schemas/PrivateNote"
          }
        },
        "example": {
          "url": "https://example.com",
          "options": {
            "case_reference": "Smith v. Jones",
            "webhook_url": "https://your-app.example/webhooks/proofsnap"
          }
        }
      },
      "CaptureAccepted": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string",
            "example": "cap_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing"
            ]
          },
          "url": {
            "type": "string"
          },
          "poll_path": {
            "type": "string",
            "description": "Statuspfad einschließlich der Stage /v1, zum Beispiel /v1/captures/cap_..."
          },
          "message": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "private_note": {
            "type": "string",
            "description": "Die private Notiz wie gespeichert (normalisiert), nur vorhanden, wenn eine gesendet wurde."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "CaptureResult": {
        "type": "object",
        "description": "Eine Aufnahme, ein Zertifikat, ein Vergleich oder eine Schwärzung. Die Felder hängen von status ab.",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "url": {
            "type": "string",
            "description": "Aufgenommene URL (null für Zertifikate und Vergleiche).",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "type": {
            "type": "string",
            "description": "comparison bei Vergleichen, import bei einem importierten Beweispaket.",
            "enum": [
              "comparison",
              "import"
            ]
          },
          "compared": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Nur bei Vergleichen: die beiden IDs der Quellaufnahmen."
          },
          "diff_summary": {
            "$ref": "#/components/schemas/DiffSummary"
          },
          "estimated_completion_seconds": {
            "type": "integer",
            "description": "Nur processing: grobe Schätzung, mindestens 5."
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "CAPTURE_FAILED",
                  "CAPTURE_TIME_BUDGET_EXCEEDED",
                  "CAPTURE_SCREENSHOT_TIMEOUT",
                  "SSRF_BLOCKED",
                  "TARGET_UNREACHABLE",
                  "PAYMENT_REQUIRED",
                  "COMPARE_PROFILE_MISMATCH",
                  "COMPARE_URL_MISMATCH",
                  "RULE902_NOT_AVAILABLE_FOR_MISMATCH",
                  "COMPARE_SOURCE_INTEGRITY_FAILED",
                  "QUOTA_EXCEEDED",
                  "CERTIFY_FAILED",
                  "REDACTION_FAILED"
                ],
                "description": "Der öffentliche Grund des Fehlschlags (siehe Fehlertabelle). CAPTURE_SCREENSHOT_TIMEOUT: Der Browser hat den Screenshot nicht fertiggestellt; CAPTURE_TIME_BUDGET_EXCEEDED: Der Durchlauf hat sein gesamtes Zeitbudget verbraucht; keines von beiden wird berechnet. Eine Vergleichszeile (cmp_) kann auch mit COMPARE_SOURCE_INTEGRITY_FAILED (ein Quellpaket wurde nach Annahme der Anfrage verändert), COMPARE_PROFILE_MISMATCH oder COMPARE_URL_MISMATCH (erst in den versiegelten Datensätzen gefunden, die der Durchlauf vergleicht) oder RULE902_NOT_AVAILABLE_FOR_MISMATCH fehlschlagen; die Einheit wird zurückgegeben. Eine im Hintergrund laufende Hash-Zertifizierung schlägt mit QUOTA_EXCEEDED fehl, wenn das monatliche Maximum bis zu ihrer Ausführung erreicht ist. CERTIFY_FAILED und REDACTION_FAILED: Eine Hash-Zertifizierung oder eine Schwärzung konnte nicht abgeschlossen werden; wiederholen Sie die Anfrage."
              },
              "message": {
                "type": "string"
              }
            },
            "description": "Nur failed."
          },
          "failed_at": {
            "type": "string",
            "description": "Nur failed.",
            "format": "date-time"
          },
          "captured_at": {
            "type": "string",
            "description": "Wann die Seite aufgenommen wurde (nur Zeilen mit completed). Bei einer Schwärzung die Aufnahmezeit ihrer Quellaufnahme (redacted_at ist der Zeitpunkt der Schwärzung).",
            "format": "date-time"
          },
          "redacted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Nur Schwärzungen: Zeitpunkt, zu dem die Schwärzung erstellt wurde. Das captured_at einer Schwärzung ist die Aufnahmezeit ihrer Quellaufnahme."
          },
          "compared_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Nur Vergleiche: Zeitpunkt, zu dem der Vergleich erstellt wurde. Sein captured_at enthält denselben Wert (für ältere Leser beibehalten); verwenden Sie compared_at."
          },
          "package_url": {
            "type": "string",
            "description": "Nur completed: vorsignierter Download-Link des Paket-ZIP, gültig bis zu 24 Stunden (siehe package_url_expires_at); lesen Sie die Aufnahme erneut, um einen neuen Link zu erhalten.",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "description": "Nur completed: wann package_url nicht mehr funktioniert, höchstens 24 Stunden nach dieser Antwort (ein mit temporären Server-Anmeldedaten signierter Link kann früher ungültig werden); lesen Sie die Aufnahme erneut, um einen neuen Link zu erhalten.",
            "format": "date-time"
          },
          "verification_url": {
            "type": "string",
            "description": "Nur completed: Link zum öffentlichen ProofSnap-Verifier.",
            "format": "uri"
          },
          "manifest_hash": {
            "type": "string",
            "description": "Nur completed: sha256:<hex> von manifest.json.",
            "example": "sha256:3f2a..."
          },
          "timestamps": {
            "$ref": "#/components/schemas/Timestamps"
          },
          "c2pa": {
            "type": "object",
            "properties": {
              "included": {
                "type": "boolean"
              },
              "signed_screenshot_sha256": {
                "type": "string",
                "description": "sha256:<hex> des signierten Screenshots.",
                "nullable": true
              }
            },
            "description": "Nur completed."
          },
          "file_count": {
            "type": "integer",
            "description": "Nur completed."
          },
          "size_bytes": {
            "type": "integer",
            "description": "Nur completed."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "consent_gated"
                  ]
                },
                "reason": {
                  "type": "string",
                  "nullable": true
                },
                "final_host": {
                  "type": "string",
                  "nullable": true
                },
                "final_url": {
                  "type": "string",
                  "nullable": true
                },
                "message": {
                  "type": "string"
                },
                "remedy": {
                  "type": "object",
                  "properties": {
                    "option": {
                      "type": "string"
                    },
                    "value": {
                      "type": "boolean"
                    }
                  }
                }
              }
            },
            "description": "Nur completed, wenn die Aufnahme auf einer Cookie-Einwilligungsseite oder Zwischenseite statt auf der Seite gelandet ist. Erneut mit dismiss_consent: true ausführen."
          },
          "external_archive": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExternalArchive"
              }
            ],
            "description": "Nur completed. Die mit der Option external_archive gestellte Anfrage an das Internet Archive: das aktuelle Ergebnis (status), der im Paket versiegelte Zustand (status_at_sealing) und der Snapshot-Link. { requested: false }, wenn keine Anfrage gestellt wurde."
          },
          "storage": {
            "$ref": "#/components/schemas/CaptureStorage"
          },
          "private_note": {
            "type": "string",
            "nullable": true,
            "description": "Ihre private Notiz (siehe PrivateNote). Wird nur dem Ersteller der Aufnahme zurückgegeben; nie Teil des Beweispakets."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "Die Aufnahme steht unter Löschsperre: Sie wird nicht gelöscht, solange true (PATCH /captures/{capture_id} mit action legal_hold). Wird automatisch aufgehoben und die Aufnahme gelöscht, wenn das Abonnement beendet wurde und seine 30 Tage ohne einen Tarif, der Daten aufbewahrt, verstrichen sind."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Wann die aktuelle Löschsperre gesetzt wurde; null ohne Sperre."
          },
          "legal_hold_reason": {
            "type": "string",
            "nullable": true,
            "description": "Der mit der aktuellen Löschsperre gespeicherte Grund; null ohne Sperre oder Grund."
          },
          "legal_hold_set_by_email": {
            "type": "string",
            "nullable": true,
            "description": "Nur Unternehmensinhaber, bei einer gesperrten Unternehmensaufnahme: die E-Mail-Adresse der Person, die die Sperre gesetzt hat."
          },
          "legal_hold_source": {
            "type": "string",
            "nullable": true,
            "description": "Woher die aktuelle Sperre stammt: \"manual\" (PATCH /captures/{capture_id}), \"bulk\" (POST /legal-holds), \"matter:<matter_id>\" (die Löschsperre des Mandats) oder \"derived:<capture_id>\" (eine Schwärzung oder ein Vergleich, gesperrt, weil eine Aufnahme, aus der sie bzw. er erstellt wurde, gesperrt ist). null ohne Sperre."
          },
          "legal_hold_released_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Wann die letzte Sperre aufgehoben wurde (Beginn der Karenzzeit nach der Aufhebung); null, solange gesperrt oder nie aufgehoben, sowie nach der automatischen Aufhebung am Ende eines Abonnements (keine Karenzzeit)."
          },
          "deletion_blocked_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Ende der Karenzzeit nach einer Aufhebung: Bis dahin wird die Aufnahme nicht endgültig gelöscht (endgültiges DELETE 409 CAPTURE_HOLD_GRACE, kein Leeren des Papierkorbs, keine Löschung nach Aufbewahrungsfrist); das Verschieben in den Papierkorb ist möglich. Die Löschung von Konto und Unternehmen nach Ende des Abonnements wartet nicht darauf. null, wenn keine Karenzzeit läuft (auch während eine Sperre aktiv ist: diese blockiert ohne Ende)."
          },
          "s3_legal_hold": {
            "type": "object",
            "nullable": true,
            "description": "Die S3-Object-Lock-Löschsperre der gespeicherten Paketobjekte (ZIP, aktualisierter OpenTimestamps-Nachweis und das separat gespeicherte Duplikat, jede Version). status: on, off, pending (wird gesetzt), failed (vom Server erneut versucht), unsupported (der Speicher hat keine Object-Lock-Konfiguration; die Sperre auf dem Aufnahmedatensatz gilt weiterhin), none (kein Paket gespeichert, z. B. bei einer fehlgeschlagenen Aufnahme).",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "on",
                  "off",
                  "pending",
                  "failed",
                  "unsupported",
                  "none"
                ]
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "source_held": {
            "type": "boolean",
            "nullable": true,
            "description": "Schwärzungen und Vergleiche: true, wenn eine Aufnahme, aus der sie erstellt wurden, unter Löschsperre steht. Ein abgeleitetes Paket folgt der Sperre seiner Quelle: Solange eine Quelle gesperrt ist, ist auch die Schwärzung oder der Vergleich gesperrt (legal_hold_source derived:<capture_id>), und die Sperre kann nicht für sich allein aufgehoben werden (409 LEGAL_HOLD_SOURCE). null für andere Aufnahmen."
          },
          "import": {
            "$ref": "#/components/schemas/CaptureImport"
          },
          "source": {
            "type": "string",
            "description": "Wie die Aufnahme erstellt wurde: api, schedule, certify-hash, compare, redact, import. GET /captures/{capture_id} gibt dies nur für ein importiertes Paket zurück (\"import\"); die Zeilen von GET /captures enthalten es immer."
          }
        }
      },
      "Timestamps": {
        "type": "object",
        "properties": {
          "bitcoin_ots": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "Status des Bitcoin-OpenTimestamps-Nachweises in manifest.json.ots. pending_confirmation: Der Nachweis wartet auf einen Bitcoin-Block (meist einige Stunden; die Prüfungen beginnen etwa 2 Stunden nach Abschluss der Aufnahme und wiederholen sich in wachsenden Abständen). verified: Ein Bitcoin-Block bestätigt den Manifest-Hash (block_height, block_time). not_present: Das Paket enthält keine manifest.json.ots (die Kalender waren beim Versiegeln des Pakets nicht erreichbar). unconfirmed: Innerhalb von 7 Tagen Prüfung wurde keine Bestätigung gefunden; der Nachweis im Paket kann weiterhin mit dem OpenTimestamps-Client aktualisiert und geprüft werden (ots upgrade, ots verify). package_missing: Das Beweispaket ist nicht mehr im Speicher, daher kann der Nachweis nicht mehr geprüft werden. manifest_mismatch: Die manifest.json im gespeicherten Paket stimmt nicht mit dem beim Versiegeln erfassten Manifest-Hash überein; der Zeitstempel wird für sie nicht bestätigt. Beide Status sind endgültig. Das Paket selbst wird nie verändert. not_checked: Die Aufnahme wurde abgeschlossen, bevor es automatische Bestätigungsprüfungen gab, und wurde nie geprüft; ihr Paket kann dennoch eine manifest.json.ots enthalten, die der OpenTimestamps-Client aktualisieren und prüfen kann. Webhook- und Erstellungsantworten werden beim Abschluss gesendet und lauten daher pending_confirmation (oder not_present, wenn die Kalender ausgefallen sind); den aktuellen Status liefert GET /captures/{capture_id}. Ein Paket wird mit ausstehendem Nachweis versiegelt: Zu diesem Zeitpunkt wurde der Manifest-Hash an die OpenTimestamps-Kalender übermittelt. Sobald er im Bitcoin-Block {block_height} ({block_time} UTC) bestätigt ist, belegt der Nachweis, dass manifest.json spätestens zu diesem Zeitpunkt existierte; laden Sie den aktualisierten Nachweis (upgraded_proof_url) herunter und legen Sie ihn als manifest.json.upgraded.ots neben manifest.json ab, dann prüfen verify.sh und verify.ps1 ihn automatisch.",
                "enum": [
                  "pending_confirmation",
                  "verified",
                  "not_present",
                  "unconfirmed",
                  "package_missing",
                  "manifest_mismatch",
                  "not_checked"
                ]
              },
              "block_height": {
                "type": "integer",
                "nullable": true,
                "description": "Bitcoin-Blockhöhe, die den Nachweis bestätigt. Gesetzt, wenn status verified ist."
              },
              "block_time": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Zeit dieses Bitcoin-Blocks. Nur bei GET /captures/{capture_id}, gesetzt, wenn status verified ist."
              },
              "verified_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Zeitpunkt, zu dem ProofSnap die Verankerung bestätigt hat. Nur bei GET /captures/{capture_id}, gesetzt, wenn status verified ist."
              },
              "upgraded_proof_url": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Nur bei GET /captures/{capture_id}. Sobald die Verankerung verifiziert ist, speichert ProofSnap den von den Kalendern aktualisierten OpenTimestamps-Nachweis (er enthält die Bestätigung durch den Bitcoin-Block und lässt sich daher offline prüfen, ohne einen Kalender zu kontaktieren). Dies ist ein vorsignierter Download-Link darauf, wie package_url bis zu 24 Stunden gültig; rufen Sie die Aufnahme erneut ab, um einen neuen Link zu erhalten. null, wenn keiner vorhanden ist (noch nicht verifiziert oder erst später durch einen Hintergrund-Backfill gespeichert). Das versiegelte Paket wird nie verändert: Speichern Sie die Datei als manifest.json.upgraded.ots neben der manifest.json des Pakets. Ab dem 2026-09-29 versiegelte Pakete prüfen sie mit ihren mitgelieferten Skripten (verification/verify.sh und verify.ps1 verwenden sie anstelle von manifest.json.ots); bei einem älteren Paket führen Sie `ots verify manifest.json.upgraded.ots -f manifest.json` aus. Mandatsexporte enthalten sie als captures/<capture_id>.upgraded.ots neben captures/<capture_id>.zip. Wird zusammen mit dem Paket gelöscht."
              }
            }
          },
          "eidas": {
            "$ref": "#/components/schemas/EidasTimestamp"
          }
        }
      },
      "DiffSummary": {
        "type": "object",
        "properties": {
          "text_insertions": {
            "type": "integer",
            "description": "Hinzugefügte Zeilen (derselbe Wert wie text_lines_added; seit dem 2026-10-14 wird der Text in Zeilen gezählt, siehe text_unit). Ältere Vergleiche zählen Zeichen. Veraltet (2026-10-14): für bestehende Leser beibehalten, nicht entfernt; verwenden Sie text_lines_added.",
            "deprecated": true
          },
          "text_deletions": {
            "type": "integer",
            "description": "Entfernte Zeilen (derselbe Wert wie text_lines_removed; seit dem 2026-10-14 Zeilen, davor Zeichen). Veraltet (2026-10-14): für bestehende Leser beibehalten, nicht entfernt; verwenden Sie text_lines_removed.",
            "deprecated": true
          },
          "text_unit": {
            "type": "string",
            "enum": [
              "line"
            ],
            "description": "Die Einheit der Textzählung: line (jede Zeile von domtextcontent.txt samt Zeilenvorschub). Fehlt bei Vergleichen vor dem 2026-10-14, die Zeichen zählten."
          },
          "text_lines_added": {
            "type": "integer",
            "description": "Hinzugefügte Zeilen (Zeilen des Nachher-Texts in hinzugefügten Abschnitten des Zeilen-Diffs)."
          },
          "text_lines_removed": {
            "type": "integer",
            "description": "Entfernte Zeilen (Zeilen des Vorher-Texts in entfernten Abschnitten des Zeilen-Diffs)."
          },
          "text_lines_before": {
            "type": "integer",
            "description": "Zeilen des Vorher-Texts."
          },
          "text_lines_after": {
            "type": "integer",
            "description": "Zeilen des Nachher-Texts."
          },
          "text_changed_percent": {
            "type": "number",
            "description": "100 x (entfernte Zeilen + hinzugefügte Zeilen) / (Zeilen vorher + Zeilen nachher), eine Nachkommastelle."
          },
          "text_diff_capped": {
            "type": "boolean",
            "description": "true, wenn der Zeilen-Diff sein Arbeitsbudget erreicht hat und ein Teil der Texte als Ganzes als ersetzt gezählt wurde (zählt eher zu viel, verbirgt nie eine Änderung)."
          },
          "metadata_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "{ key, before, after, class } für jedes abweichende Feld von metadata.json, und zwar genau diese: url, final_url, page_title, http_status, http_headers.content-type, http_headers.server, dns.connected_ip, resolved_ips (als sortierte Liste verglichen), tls.issuer, tls.valid_to sowie die Geräteprofilfelder viewport_width, viewport_height, device_scale_factor, is_mobile und user_agent. class ist content (url, final_url, page_title, http_status, http_headers.content-type: als Änderung gezählt), infrastructure (http_headers.server, dns.connected_ip, resolved_ips, tls.issuer, tls.valid_to: welcher Server die Seite ausgeliefert hat, nur berichtet) oder device_profile (wie die Aufnahme die Seite gerendert hat, nur berichtet). Ein Vergleich vor dem 2026-10-10 hat keine class."
          },
          "meta_content": {
            "type": "integer",
            "description": "Abweichende Metadatenfelder der Klasse content; sie zählen für changed."
          },
          "meta_infra": {
            "type": "integer",
            "description": "Abweichende Metadatenfelder der Klasse infrastructure (Serversoftware, verbundene IP, aufgelöste IPs, TLS-Aussteller und -Ablauf). Ein Content Delivery Network antwortet von wechselnden Adressen, daher können sich zwei Abrufe einer unveränderten Seite hier unterscheiden; nur zur Information berichtet, nie für changed gezählt."
          },
          "meta_device_profile": {
            "type": "integer",
            "description": "Abweichende Geräteprofilfelder (Viewport, Skalierung, mobile Emulation, User-Agent); nur zur Information berichtet, nie für changed gezählt."
          },
          "image_changes": {
            "type": "object",
            "description": "Der Bildvergleich, auf Basis des Bildinventars, das jede Quellaufnahme versiegelt hat (image_inventory.json). status ran, oder not_run mit reason source_without_inventory (eine Quelle, die aufgenommen wurde, bevor Aufnahmen ein Inventar erfassten), source_inventory_failed oder source_inventory_unreadable; ersatzweise wird nichts anderes verwendet. Die vollständigen Ergebnisse je Bild stehen in image_changes.json des Pakets.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ran",
                  "not_run"
                ]
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "source_without_inventory",
                  "source_inventory_failed",
                  "source_inventory_unreadable",
                  null
                ],
                "description": "Warum die Bilder nicht verglichen wurden: source_without_inventory (eine Quelle, die erstellt wurde, bevor Aufnahmen ein Bildinventar erfassten; neue Aufnahmen erfassen eines), source_inventory_failed (das Inventar konnte bei der Erstellung dieser Aufnahme nicht erfasst werden) oder source_inventory_unreadable; null, wenn der Vergleich lief."
              },
              "images_changed": {
                "type": "integer",
                "nullable": true,
                "description": "Ersetzte, hinzugefügte oder entfernte First-Party-Bilder (ausgeliefert von der eigenen registrierbaren Domain der Seite oder seit dem 2026-10-14 von einer Domain, die die Seite als ihr eigenes og:image oder Icon angibt oder die in beiden Aufnahmen die meisten Bilder der Seite ausliefert: image_changes.json site_image_hosts), der Bildanteil von changed; verschobene Bilder sind nicht enthalten. null, wenn der Vergleich nicht lief. Veraltet (2026-10-14): für bestehende Leser beibehalten, nicht entfernt; verwenden Sie site_images_changed.",
                "deprecated": true
              },
              "first_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "third_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "tracking_pixel": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "inventories_truncated": {
                "type": "boolean",
                "description": "true, wenn ein Quellinventar seine Eintragsgrenze erreicht hat; siehe inventory_cutoff."
              },
              "site_images_changed": {
                "type": "integer",
                "description": "Dasselbe wie images_changed (gezählt)."
              },
              "other_images_changed": {
                "type": "integer",
                "description": "Ersetzte, entfernte oder hinzugefügte Bilder anderer Domains und Tracking-Pixel (nie gezählt)."
              },
              "site_image_hosts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Registrierbare Domains, die neben der der Seite als eigene Domains der Website gezählt werden (site_image_hosts von image_changes.json)."
              },
              "inventory_cutoff": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "before_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Ein nur in Vorher gefundenes Bild ist entscheidbar (entfernt), wenn sein Index im Vorher-Inventar unter diesem Wert liegt; null: keine Grenze (das Nachher-Inventar ist vollständig)."
                  },
                  "after_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Ein nur in Nachher gefundenes Bild ist entscheidbar (hinzugefügt), wenn sein Index im Nachher-Inventar unter diesem Wert liegt; null: keine Grenze (das Vorher-Inventar ist vollständig)."
                  }
                },
                "description": "Seit dem zweiten Audit vom 2026-10-14 (image_changes.json algorithm 3) ein Objekt, entschieden nach Dokumentindex: Hat das ANDERE Inventar seine Eintragsgrenze erreicht, ist ein nur auf einer Seite gefundenes Bild nur dann entscheidbar, wenn sein Index unter der Eintragsanzahl des anderen Inventars abzüglich einer Marge liegt (der größere Wert von 10 Einträgen und 5 Prozent dieser Anzahl); andernfalls ist es unbestimmt (undetermined_reason beyond_inventory_limit). null, wenn kein Inventar abgeschnitten ist. Ältere Vergleiche enthalten eine Zahl (die relative Dokumentposition der früheren Regel)."
              }
            }
          },
          "changed": {
            "type": "boolean",
            "nullable": true,
            "description": "true, wenn sich der sichtbare Text unterscheidet, ein Metadatenfeld der Klasse content abweicht (meta_content) oder ein First-Party-Bild ersetzt, entfernt oder hinzugefügt wurde (image_changes.images_changed). Infrastruktur- und Geräteprofilfelder, verschobene Bilder (dieselben Bytes unter einer anderen Adresse), Bilder von Drittanbietern und Tracking-Pixel werden berichtet, setzen den Wert aber nicht. null (seit dem 2026-10-14), wenn sich nichts Gezähltes verlässlich geändert hat, das Text- oder Bildergebnis aber unbestimmt ist (unterschiedlich gerenderte Aufnahmen, allow_profile_mismatch). Das Paket nennt die Regel in comparison_method.json counted_as_change. Veraltet (2026-10-14): für bestehende Leser beibehalten, nicht entfernt; verwenden Sie verdict (changed | unchanged | undetermined) und die artbezogenen text_verdict, images_verdict, metadata_verdict.",
            "deprecated": true
          },
          "text_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "undetermined, wenn die beiden Aufnahmen mit einem anderen Geräteprofil oder anderen Aufnahmeeinstellungen gerendert wurden (allow_profile_mismatch)."
          },
          "images_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined",
              "not_run"
            ]
          },
          "metadata_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged"
            ],
            "description": "Aus den Metadatenfeldern der Klasse content (Adresse, endgültige Adresse, Titel, HTTP-Status, Content-Type)."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "Das Gesamtergebnis in Worten: changed, wenn changed true ist, undetermined, wenn changed null ist."
          },
          "undetermined_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "render_profile_mismatch",
              "capture_settings_mismatch",
              null
            ]
          },
          "text_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Nur bei GET: bis zu 60 geänderte Textabschnitte."
          },
          "change_regions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Nur bei GET: bis zu 60 auf den Screenshots verortete Textänderungen (aus text_layout.json der Quellen), in Screenshot-Pixeln. Jede Änderung enthält counterpart_y_px (ihre vertikale Position auf dem anderen Screenshot, übernommen vom nächstgelegenen unveränderten Textblock) und report_region (die Nummer der Änderungsstelle, in der evidence.pdf sie zeigt, höchstens 10 Stellen, oder null) wie in change_regions.json."
          },
          "change_regions_info": {
            "type": "object",
            "nullable": true
          },
          "order_reversed": {
            "type": "boolean",
            "description": "Nur bei GET: true, wenn die als capture_id_1 (Vorher) angegebene Aufnahme nach der als capture_id_2 (Nachher) angegebenen aufgenommen wurde, entsprechend compare.json order_reversed. Der Vergleich behält die angegebene Reihenfolge bei; Bericht und Dashboard weisen darauf hin."
          },
          "render_profile_mismatch": {
            "type": "boolean",
            "description": "Nur bei GET: Die beiden Aufnahmen wurden mit einem anderen Geräteprofil gerendert (compare.json)."
          },
          "capture_settings_mismatch": {
            "type": "boolean",
            "description": "Nur bei GET: Die beiden Aufnahmen wurden mit anderen Aufnahmeeinstellungen erstellt (compare.json)."
          },
          "capture_settings_differing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "setting": {
                  "type": "string"
                },
                "before": {},
                "after": {}
              }
            },
            "description": "Nur bei GET: die abweichenden Einstellungen, { setting, before, after }."
          },
          "profile_mismatch_allowed": {
            "type": "boolean",
            "description": "Nur bei GET: Der Vergleich wurde mit allow_profile_mismatch für unterschiedlich gerenderte Aufnahmen erstellt. Veraltet (2026-10-14): für bestehende Leser beibehalten, nicht entfernt; verwenden Sie render_profile_mismatch und capture_settings_mismatch.",
            "deprecated": true
          },
          "url_mismatch": {
            "type": "boolean",
            "description": "Nur bei GET: Die beiden Aufnahmen haben keine gemeinsame Adresse (erstellt mit allow_url_mismatch)."
          }
        },
        "description": "Zusammenfassung des Vergleichs."
      },
      "ImageChangeCounts": {
        "type": "object",
        "properties": {
          "unchanged": {
            "type": "integer"
          },
          "replaced": {
            "type": "integer"
          },
          "moved": {
            "type": "integer"
          },
          "removed": {
            "type": "integer"
          },
          "added": {
            "type": "integer"
          },
          "undetermined": {
            "type": "integer"
          },
          "alt_changed": {
            "type": "integer"
          },
          "position_changed": {
            "type": "integer"
          },
          "undetermined_same_error": {
            "type": "integer",
            "description": "Unbestimmte Paare, deren beide Seiten denselben HTTP-Fehlerstatus lieferten (zum Beispiel 404 in beiden Aufnahmen); in undetermined enthalten, im Bericht gezählt und nicht aufgelistet."
          },
          "occurrences": {
            "type": "integer",
            "description": "Alle Ergebnisse dieser Herkunftskategorie: Vorkommen auf der Seite."
          },
          "distinct": {
            "type": "object",
            "description": "Die Anzahl verschiedener Bilder unter den Ergebnissen, insgesamt und je Kategorie (ein mehrfach angezeigtes Bild zählt einmal): Jedes Ergebnis steht für den SHA-256 seiner Nachher-Seite oder, falls es keine hat, seiner Vorher-Seite, oder für die Adresse dieser Seite, wenn sie keinen SHA-256 trägt.",
            "properties": {
              "total": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "replaced": {
                "type": "integer"
              },
              "moved": {
                "type": "integer"
              },
              "removed": {
                "type": "integer"
              },
              "added": {
                "type": "integer"
              },
              "undetermined": {
                "type": "integer"
              }
            }
          }
        },
        "description": "Anzahlen je Kategorie als Vorkommen auf der Seite (ein dreimal angezeigtes Bild zählt dreimal): unchanged, replaced (gleiche Adresse, andere Bytes), moved (andere Adresse, gleiche Bytes; berichtet, nicht als Änderung gezählt), removed, added, undetermined (ein Bild ohne SHA-256 auf beiden Seiten, nie als entfernt oder hinzugefügt gezählt); dazu die gepaarten Bilder, deren Alt-Text oder Position unter den Bildern der Seite sich geändert hat, die unbestimmten Paare mit demselben HTTP-Fehler auf beiden Seiten sowie die verschiedenen Bilder."
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048
            },
            "minItems": 2,
            "maxItems": 50,
            "description": "2 bis 50 URLs, jede wie CaptureRequest.url validiert. Hostnamen werden bei der Annahme des Batches nicht per DNS geprüft; eine URL, die die DNS- oder Adressprüfung nicht besteht, scheitert später mit status failed (und capture.failed)."
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PrivateNote"
              }
            ],
            "description": "Eine private Notiz, die bei jeder Aufnahme des Batches gespeichert wird. Ihre private Notiz zur Aufnahme (zum Beispiel „mobile Aufnahme“), höchstens 200 Zeichen, reiner Text (die PlainText-Regel). Sie wird nur im Aufnahmedatensatz gespeichert: Sie ist NICHT Teil des versiegelten Beweispakets (nicht in der ZIP, im PDF, im Manifest oder in einem Hash) und hat keine Beweiskraft, sodass sie später mit PATCH /v1/captures/{capture_id} geändert oder gelöscht werden kann, ohne die Beweise zu beeinflussen. Nur der Ersteller der Aufnahme kann sie lesen. Lassen Sie sie weg oder senden Sie null oder eine leere Zeichenkette, wenn Sie keine Notiz wünschen."
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "example": "batch_a1B2c3D4e5F6g7H8"
          },
          "count": {
            "type": "integer"
          },
          "captures": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "capture_id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "failed, wenn die Aufnahme nicht gestartet werden konnte.",
                  "enum": [
                    "processing",
                    "failed"
                  ]
                }
              }
            }
          },
          "not_admitted": {
            "type": "array",
            "description": "Nur wenn das Limit gleichzeitig laufender Aufnahmen des Kontos erreicht wurde: die nicht gestarteten URLs in der gesendeten Reihenfolge. Sie kosten nichts (keine Rate-Limit- oder Monatseinheit); senden Sie sie später erneut. Fehlt, wenn jede URL gestartet wurde.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "description": "Warum die URL nicht gestartet wurde.",
                  "enum": [
                    "CONCURRENT_CAPTURE_LIMIT"
                  ]
                }
              }
            }
          },
          "in_flight_limit": {
            "type": "integer",
            "description": "Nur zusammen mit not_admitted: die Anzahl der Aufnahmen, die das Konto gleichzeitig laufen lassen darf (developer 10, startup 25, growth 100, legal_solo und legal_seats 25, custom 200, website_archive 25, sandbox 5)."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "properties": {
          "verdict": {
            "type": "string",
            "nullable": true,
            "description": "match: Signatur gültig und jede aufgelistete Datei mit ihrem erfassten SHA-256 vorhanden. modified: Signatur ungültig oder eine Datei fehlt oder wurde verändert. invalid: Das Paket konnte nicht geprüft werden. null nur bei einem importierten Paket, das diesmal nicht erneut geprüft wurde (`rechecked: false`, mit `reason` und der beim Import erfassten Prüfung in `stored_verification`).",
            "enum": [
              "match",
              "modified",
              "invalid",
              null
            ]
          },
          "reason": {
            "type": "string",
            "description": "Nur bei invalid; bei einem importierten Paket mit `rechecked: false` der Grund, warum es nicht erneut geprüft wurde (too_large_for_synchronous_check, time_budget, storage_error)."
          },
          "capture_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "s3"
            ]
          },
          "checks": {
            "type": "object",
            "properties": {
              "manifest_signature": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "valid",
                      "invalid"
                    ]
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "RSA-4096"
                    ]
                  }
                }
              },
              "declared_files": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "all_match",
                      "mismatch"
                    ]
                  },
                  "mismatched": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "missing": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Nur das Vorhandensein von manifest.json.ots; der Nachweis wird hier nicht geprüft.",
                    "enum": [
                      "present",
                      "not_present"
                    ]
                  }
                }
              },
              "eidas_disig": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Vorhandensein von manifest.json.tsr. Das Token selbst wird in checks.eidas geprüft. missing: manifest.json erfasst eidas_requested (das Paket wurde mit dem Token ausgestellt), aber manifest.json.tsr ist nicht im Paket.",
                    "enum": [
                      "present",
                      "missing"
                    ]
                  }
                },
                "nullable": true,
                "description": "null, wenn keine manifest.json.tsr vorhanden ist und manifest.json kein eidas_requested erfasst."
              },
              "proofsnap_issuance": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "issued_by_proofsnap: Der Manifest-Hash steht im ProofSnap-Ausstellungsregister. unverified: Er steht nicht darin (Pakete, die vor Einführung des Registers ausgestellt wurden, sind nicht enthalten). unknown: Die Abfrage ist fehlgeschlagen.",
                    "enum": [
                      "issued_by_proofsnap",
                      "unverified",
                      "unknown"
                    ]
                  },
                  "manifest_sha256": {
                    "type": "string"
                  },
                  "issued_at": {
                    "type": "string",
                    "nullable": true,
                    "format": "date-time"
                  },
                  "type": {
                    "type": "string"
                  }
                }
              },
              "eidas": {
                "type": "object",
                "nullable": true,
                "description": "null, wenn keine manifest.json.tsr vorhanden ist und manifest.json kein eidas_requested erfasst. Das eIDAS-Zeitstempel-Token, wie es ProofSnap auf Anfrage anhand des gespeicherten Pakets geprüft hat. Dies ist eine von ProofSnap durchgeführte Prüfung; das Feld note beschreibt, wie sich das Token unabhängig prüfen lässt.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "verified_revocation_unknown",
                      "failed",
                      "unavailable",
                      "missing"
                    ],
                    "description": "missing: manifest.json erfasst eidas_requested: true, das Paket wurde also mit einem qualifizierten Zeitstempel ausgestellt, aber manifest.json.tsr ist nicht darin (die Antwort enthält dann die Warnung EIDAS_TOKEN_MISSING). verified: Jede Prüfung wurde bestanden und der bei der Ausstellung erfasste Status des TSA-Zertifikats ist bekannt (good, oder nach dem Token-Zeitpunkt aus einem anderen Grund als Schlüsselkompromittierung widerrufen). verified_revocation_unknown: Jede durchgeführte Prüfung wurde bestanden, aber der Status des TSA-Zertifikats ließ sich aus den erfassten Daten nicht feststellen (revocation.status unknown oder not_checked). failed: Mindestens eine Prüfung ist fehlgeschlagen, siehe failures (die Antwort enthält dann die Warnung EIDAS_TOKEN_FAILED). unavailable: kein Urteil über das Token (reason: not configured, timeout, input_rejected, wenn der Verifier die Eingabe abgelehnt hat, etwa zu große Validierungsdaten, inconclusive); über das Token wird nichts ausgesagt."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Nur bei unavailable: timeout, input_rejected, inconclusive oder eine kurze Erklärung."
                  },
                  "checked_by": {
                    "type": "string",
                    "example": "ProofSnap API on request"
                  },
                  "checked_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "gen_time": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "Die Zeit im Token (TSTInfo genTime)."
                  },
                  "tsa_name": {
                    "type": "string",
                    "nullable": true,
                    "description": "Aus dem Unterzeichnerzertifikat (Common Name und Organisation), nie ein Standardwert."
                  },
                  "serial_number": {
                    "type": "string",
                    "nullable": true
                  },
                  "policy_oid": {
                    "type": "string",
                    "nullable": true
                  },
                  "hash_algorithm": {
                    "type": "string",
                    "nullable": true
                  },
                  "signature_algorithm": {
                    "type": "string"
                  },
                  "signing_certificate_form": {
                    "type": "string",
                    "enum": [
                      "ESSCertIDv2",
                      "ESSCertID"
                    ]
                  },
                  "signer_certificate": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "subject": {
                        "type": "string"
                      },
                      "issuer": {
                        "type": "string"
                      },
                      "serial_number": {
                        "type": "string"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "not_before": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "not_after": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  },
                  "root": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string",
                        "example": "CA Disig QCA4"
                      },
                      "sha256": {
                        "type": "string"
                      }
                    }
                  },
                  "chain_checked_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "Der Zeitpunkt, auf den die Kette geprüft wurde: die genTime des Tokens, sodass ein Zertifikat, das nach Ausstellung des Tokens abgelaufen ist, nicht zum Fehlschlag führt."
                  },
                  "chain": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "sha256": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "revocation": {
                    "type": "object",
                    "description": "Status des TSA-Zertifikats aus den bei der Ausstellung erfassten Daten (basis recorded_at_issuance), per Signatur geprüft und mit der Anforderung, zur genTime aktuell gewesen zu sein. Nicht live abgerufen.",
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "good",
                          "revoked",
                          "revoked_after_gen_time",
                          "unknown",
                          "not_checked"
                        ],
                        "description": "revoked_after_gen_time: nach dem Token-Zeitpunkt aus einem anderen Grund als Schlüsselkompromittierung widerrufen (das Token bleibt verifiziert). not_checked: Es wurden keine Widerrufsdaten erfasst oder geprüft."
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "ocsp",
                          "crl"
                        ],
                        "nullable": true
                      },
                      "basis": {
                        "type": "string",
                        "enum": [
                          "recorded_at_issuance"
                        ]
                      },
                      "produced_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "this_update": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "next_update": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      },
                      "reason": {
                        "type": "string",
                        "description": "Warum der Status unbekannt ist."
                      }
                    }
                  },
                  "checks": {
                    "type": "object",
                    "description": "Jede Prüfung: passed, failed, not_applicable (nonce: bei einem gespeicherten Token keine erfasst), unknown (Widerruf ohne verwertbare Daten) oder not_checked (ein früherer Fehlschlag hat die Prüfung beendet).",
                    "properties": {
                      "response_status": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_imprint": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "nonce": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "content_type": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_digest": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signature": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signing_certificate": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "timestamping_eku": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "chain": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "revocation": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      }
                    }
                  },
                  "failures": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "check": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "note": {
                    "type": "string"
                  },
                  "detail": {
                    "type": "string",
                    "nullable": true,
                    "description": "Nur bei input_rejected: was der Verifier abgelehnt hat."
                  }
                }
              }
            },
            "description": "Fehlt, wenn verdict invalid ist. Das Urteil hängt nur von manifest_signature und declared_files ab; eidas wird daneben berichtet und, falls fehlgeschlagen, in warnings."
          },
          "warnings": {
            "type": "array",
            "description": "Nur vorhanden, wenn etwas außerhalb des Urteils Aufmerksamkeit erfordert. EIDAS_TOKEN_FAILED: manifest.json.tsr hat die Prüfung in checks.eidas nicht bestanden. EIDAS_TOKEN_MISSING: manifest.json erfasst eidas_requested, aber manifest.json.tsr ist nicht im Paket.",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "EIDAS_TOKEN_FAILED",
                    "EIDAS_TOKEN_MISSING"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "required": [
          "url",
          "mode"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Aufzunehmende Seite, validiert wie CaptureRequest.url.",
            "format": "uri",
            "maxLength": 2048
          },
          "mode": {
            "type": "string",
            "description": "interval: alle interval_seconds. cron: gemäß cron_expr. on_change: die Seite alle interval_seconds prüfen und nur aufnehmen, wenn sich ihr Text geändert hat.",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "description": "Erforderlich für interval und on_change. Mindestens das Tarifminimum (300 bei developer, startup, growth, custom, legal_solo und legal_seats; 86400 bei website_archive), andernfalls 400 INTERVAL_TOO_SHORT.",
            "minimum": 300
          },
          "cron_expr": {
            "type": "string",
            "description": "Erforderlich für cron (höchstens 200 Zeichen). Ein nicht parsbarer Ausdruck liefert 400 INVALID_CRON; zwei aufeinanderfolgende Läufe, die näher beieinanderliegen als das Tarifminimum, liefern 400 INTERVAL_TOO_SHORT.",
            "maxLength": 200,
            "example": "0 9 * * 1-5"
          },
          "timezone": {
            "type": "string",
            "description": "IANA-Zeitzone zur Auswertung von cron_expr. Standard UTC.",
            "example": "Europe/Berlin"
          },
          "webhook_url": {
            "type": "string",
            "description": "https-URL. Läufe mit interval und cron senden capture.completed oder capture.failed; on_change sendet monitor.change_detected. Die URL muss https verwenden und darf nicht auf eine private, Loopback-, Link-Local-, Carrier-Grade-NAT- oder Cloud-Metadaten-Adresse zeigen (dieselben Regeln wie für eine Aufnahme-URL; andernfalls 422 SSRF_BLOCKED). Ein leerer oder nur aus Leerraum bestehender Wert bedeutet keinen Webhook. Ein Zeitplan hat genau eine webhook_url, auf oberster Ebene: Eine options.webhook_url wird nicht gespeichert (wird keine webhook_url auf oberster Ebene gesendet, dient sie als solche), daher sendet ein on_change-Zeitplan nie zusätzlich capture.completed. Erfordert einen API-Schlüssel: Aus einer Dashboard-Sitzung gesendet, wird sie mit 400 WEBHOOK_REQUIRES_API_KEY abgelehnt, weil Webhooks mit dem Webhook-Secret des Schlüssels signiert werden.",
            "format": "uri"
          },
          "js_render": {
            "type": "boolean",
            "description": "Nur bei on_change: die Seite mit einem vollständigen Browser-Rendering statt mit einem einfachen HTTP-Abruf prüfen (für Seiten, die JavaScript benötigen).",
            "default": false
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true,
            "minimum": 86400,
            "maximum": 34560000,
            "description": "Nur bei on_change: zusätzlich eine vollständige Aufnahme erstellen (sie sendet capture.completed), wenn die neueste Aufnahme des Zeitplans älter als dieser Wert ist, auch wenn sich die Seite nicht geändert hat. Für Vorgaben, die mindestens alle N Tage eine Kopie verlangen, z. B. 7776000 für 90 Tage. Von 1 Tag bis 400 Tage und länger als interval_seconds; null (Standard) nimmt nur bei einer Änderung auf. PATCH mit null entfernt den Wert."
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "Nur bei on_change (2026-10-14): welche erkannte Änderung monitor.change_detected sendet. any (Standard, das bisherige Verhalten): jede Änderungsaufnahme. text: nur, wenn der Vergleich mit der vorherigen Aufnahme eine Änderung des sichtbaren Texts feststellt (diff_summary.text_verdict changed); eine Änderungsaufnahme, deren Vergleich ergibt, dass sich der Text nicht geändert hat oder dies nicht feststellbar war, sendet nichts (die Aufnahme wird trotzdem erstellt, abgerechnet und sendet capture.completed an Abonnements). Ohne Vergleichsergebnis (keine Ausgangsbasis, Vergleich fehlgeschlagen) wird alarmiert. 400 MALFORMED_REQUEST bei einem anderen Modus oder Wert. null bei Zeitplänen mit interval und cron."
          }
        },
        "example": {
          "url": "https://example.com/terms",
          "mode": "on_change",
          "interval_seconds": 3600,
          "webhook_url": "https://your-app.example/webhooks/proofsnap"
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "schedule_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "nullable": true
          },
          "cron_expr": {
            "type": "string",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "webhook_url": {
            "type": "string",
            "nullable": true,
            "description": "Der Webhook des Zeitplans (bei einem älteren Zeitplan, der ihn in options.webhook_url geführt hat, wird dieser Wert hier angezeigt und options enthält ihn nie). Erfordert einen API-Schlüssel: Aus einer Dashboard-Sitzung gesendet, wird er mit 400 WEBHOOK_REQUIRES_API_KEY abgelehnt, weil Webhooks mit dem Webhook-Secret des Schlüssels signiert werden."
          },
          "options": {
            "type": "object",
            "description": "Aufnahmeoptionen mit angewendeten Standardwerten."
          },
          "js_render": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ]
          },
          "next_run_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_run_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "last_capture_id": {
            "type": "string",
            "nullable": true
          },
          "run_count": {
            "type": "integer"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "pause_reason": {
            "type": "string",
            "description": "Warum der Zeitplan automatisch pausiert wurde: max_failures (5 aufeinanderfolgende fehlgeschlagene Läufe), no_billing, plan_downgrade (Tariflimits oder ein Archive-only-Tarif), invalid_cron oder key_revoked. null, wenn aktiv oder von Ihnen pausiert.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true
          },
          "current_capture_id": {
            "type": "string",
            "nullable": true,
            "description": "Die neueste ABGESCHLOSSENE Aufnahme des Zeitplans (ihre aktuelle Version; last_capture_id ist die zuletzt gestartete)."
          },
          "current_captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "on_change: any oder text (any bei einem Zeitplan, der vor Einführung der Option erstellt wurde); null bei interval und cron."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "Matter": {
        "type": "object",
        "properties": {
          "matter_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "client": {
            "type": "string",
            "nullable": true
          },
          "case_number": {
            "type": "string",
            "nullable": true
          },
          "opposing_party": {
            "type": "string",
            "nullable": true
          },
          "jurisdiction": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "archived",
              "sealed"
            ]
          },
          "sealed": {
            "type": "boolean"
          },
          "capture_count": {
            "type": "integer"
          },
          "custody_count": {
            "type": "integer",
            "description": "Einträge im Beweiskettenprotokoll."
          },
          "custody_head_hash": {
            "type": "string",
            "description": "sha256:<hex> des neuesten Eintrags im Beweiskettenprotokoll.",
            "nullable": true
          },
          "seal": {
            "$ref": "#/components/schemas/MatterSeal",
            "description": "null bis zur Versiegelung."
          },
          "member_uids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_by": {
            "type": "string",
            "description": "Der Inhaber.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "read_only": {
            "type": "boolean",
            "description": "Vorhanden (true) bei einem Mandat eines Unternehmens, das beendet wurde oder als Firmenarchiv aufbewahrt wird: nur Öffnen, Export und Widerruf von Freigaben."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "Die Löschsperre des Mandats ist aktiv (PATCH { legal_hold })."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MatterSeal": {
        "type": "object",
        "properties": {
          "root_hash": {
            "type": "string",
            "description": "sha256:<hex>: der Head der Beweiskette, der versiegelt und an OpenTimestamps übermittelt wurde."
          },
          "ots": {
            "type": "string",
            "description": "OpenTimestamps-Nachweis, hex."
          },
          "ots_pending": {
            "type": "boolean"
          },
          "eidas": {
            "type": "object",
            "properties": {
              "tsr_base64": {
                "type": "string",
                "nullable": true
              },
              "serial": {
                "type": "string",
                "nullable": true
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "ltv": {
                "type": "boolean"
              },
              "verification_status": {
                "type": "string",
                "enum": [
                  "verified"
                ],
                "description": "Das Token hat bei der Versiegelung die ProofSnap-Prüfung bestanden (gemeinsamer Verifier, dieselben Prüfungen wie bei einem Aufnahme-Token)."
              },
              "revocation_status": {
                "type": "string",
                "enum": [
                  "good",
                  "revoked_after_gen_time",
                  "unknown",
                  "not_checked"
                ],
                "description": "Status des TSA-Zertifikats, bei der Versiegelung aus der damals eingeholten OCSP-Antwort oder CRL festgestellt."
              },
              "validation": {
                "type": "object",
                "nullable": true,
                "description": "Der bei der Versiegelung erfasste Inhalt von eidas_validation.json für eine Offline-Prüfung des Tokens: tsaCertChain, rootCaPem, ocspResponse, crlResponse (bei zu großem Umfang weggelassen und in omitted genannt), ocspUrl, ocspFetchedAt, ocspStatus, crlUrl, verification. Versiegelungen vor dem 2026-10-01 haben keinen.",
                "additionalProperties": true
              }
            },
            "nullable": true,
            "description": "eIDAS-Zeitstempel über denselben Hash, wenn in der Bereitstellung eIDAS konfiguriert ist, der Aufrufer live ist und ein Token bezogen wurde, das die Prüfung bestanden hat, sonst null (siehe eidas_status)."
          },
          "sealed_by": {
            "type": "string",
            "nullable": true
          },
          "sealed_at": {
            "type": "string",
            "format": "date-time"
          },
          "eidas_status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained",
              "not_configured"
            ],
            "description": "Fehlt bei Versiegelungen vor dem 2026-10-01. not_obtained: Die Versiegelung trägt nur die OpenTimestamps-Verankerung (eidas_reason token_refused, unavailable oder test_key). Eine Zeitüberschreitung des eIDAS-Stempels erzeugt nie eine Versiegelung: Die Anfrage liefert stattdessen 503 EIDAS_SEAL_RETRY."
          },
          "eidas_reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "test_key",
              "account_limit"
            ],
            "description": "Nur bei not_obtained. test_key: von einem Test-Aufrufer versiegelt (Testschlüssel oder Sandbox-Sitzung), daher wurde kein eIDAS-Zeitstempel angefordert. account_limit: Das Konto hat noch keine bezahlte Rechnung und hat sein eIDAS-Limit erreicht (20 pro UTC-Tag, 50 pro UTC-Monat)."
          }
        },
        "nullable": true
      },
      "CustodyEntry": {
        "type": "object",
        "properties": {
          "seq": {
            "type": "integer"
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "enum": [
              "api",
              "user"
            ],
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "Zum Beispiel matter.created, matter.updated, capture.added, capture.removed, capture.legal_hold_placed, capture.legal_hold_released, member.added, member.removed, package.imported (ein mit matter_id importiertes Beweispaket; target ist die Aufnahme-ID)."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "prev_hash": {
            "type": "string"
          },
          "entry_hash": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "Nur bei package.imported: manifest_sha256, zip_sha256, package_kind, verdict, levels. Mit dem Eintrag gespeichert, aber NICHT von entry_hash abgedeckt.",
            "additionalProperties": true
          }
        },
        "description": "Ein Glied der Hash-Kette. entry_hash deckt seq, ts, actor, action, target und prev_hash ab."
      },
      "MatterCapture": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string"
          },
          "captured_at": {
            "type": "string",
            "nullable": true
          },
          "file_count": {
            "type": "integer",
            "nullable": true
          },
          "manifest_hash": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "ts": {
            "type": "string"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "Zum Beispiel capture.downloaded, export.generated, share.created, share.revoked."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "source_ip": {
            "type": "string",
            "nullable": true,
            "description": "Nur für den Inhaber des Mandats (fehlt bei Mitgliedern)."
          },
          "user_agent": {
            "type": "string",
            "nullable": true,
            "description": "Nur für den Inhaber des Mandats (fehlt bei Mitgliedern)."
          }
        },
        "description": "Zugriffsereignis. Nicht Teil der Hash-Kette."
      },
      "CertifyResult": {
        "type": "object",
        "properties": {
          "certify_id": {
            "type": "string",
            "example": "cert_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "hash_sha256": {
            "type": "string",
            "description": "Nur bei einem Einzelzertifikat."
          },
          "combined": {
            "type": "boolean",
            "description": "Nur bei einem kombinierten Zertifikat."
          },
          "file_count": {
            "type": "integer",
            "description": "Nur bei einem kombinierten Zertifikat."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Die von Ihnen gesendete Bezeichnung.",
                  "nullable": true
                },
                "sha256": {
                  "type": "string"
                }
              }
            },
            "description": "Nur bei einem kombinierten Zertifikat."
          },
          "label": {
            "type": "string",
            "description": "Ihre Bezeichnung oder \"N files\" bei einem kombinierten Zertifikat.",
            "nullable": true
          },
          "certified_at": {
            "type": "string",
            "format": "date-time"
          },
          "timestamps": {
            "type": "object",
            "properties": {
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending_confirmation",
                      "not_present"
                    ],
                    "description": "pending_confirmation: manifest.json.ots ist im Paket und wartet auf einen Bitcoin-Block. not_present: Die OpenTimestamps-Kalender waren nicht erreichbar, das Paket hat keine manifest.json.ots und manifest.json listet sie nicht auf."
                  },
                  "block_height": {
                    "type": "integer",
                    "nullable": true
                  }
                }
              },
              "eidas": {
                "$ref": "#/components/schemas/EidasTimestamp"
              }
            }
          },
          "package_url": {
            "type": "string",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "verify_url": {
            "type": "string",
            "description": "Link zum öffentlichen ProofSnap-Verifier.",
            "format": "uri"
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "PlainText": {
        "type": "string",
        "description": "Regel für reinen Text bei kurzen Freitextfeldern, die auf gerichtsrelevanten Dokumenten oder öffentlichen Seiten gedruckt werden (case_reference, case_id, case_note, Werte von evidence_pack.custodian, die case_reference der Hash-Zertifizierung, der custodian der Schwärzung, die Exporterklärung, Unternehmens- und Mandatsnamen). Der Wert wird NFC-normalisiert, Leerraum wird zusammengefasst und am Anfang und Ende entfernt, und die Länge wird in Zeichen (Unicode-Codepoints) gezählt. Abgelehnt werden: Steuerzeichen (C0, C1), Bidi-Einbettungen, -Überschreibungen und -Isolierungen (U+202A bis U+202E, U+2066 bis U+2069), Richtungsmarken (U+200E, U+200F, U+061C), Zeichen ohne Breite und unsichtbare Zeichen (U+200B, U+2060 bis U+2064, U+FEFF, U+00AD, U+034F, U+180E, U+206A bis U+206F, U+FFF9 bis U+FFFB), die Hangul-Füllzeichen (U+115F, U+1160, U+3164, U+FFA0), Tag-Zeichen (U+E0000 bis U+E007F), einzelne Surrogate und Zeichen aus dem Bereich für private Nutzung. Buchstaben aller Schriften sind zulässig, darunter Hangul-Silben, Arabisch, Hebräisch, Devanagari und CJK, und U+200C / U+200D (ZWNJ, ZWJ) sind erlaubt, weil die korrekte Schreibweise in mehreren Schriften sie erfordert."
      },
      "EidasTimestamp": {
        "type": "object",
        "description": "eIDAS-Ergebnis, wenn include_eidas mit einem Live-API-Schlüssel angefordert wurde; fehlt, wenn es nicht angefordert wurde. confirmed: Das Paket enthält manifest.json.tsr (abgerechnet, außer der Widerrufsstatus ist unbekannt). not_obtained: Es wurde kein Token ausgeliefert und nichts abgerechnet; das Paket trägt nur die OpenTimestamps-Verankerung (reason token_refused: ein Token kam zurück und hat die Prüfung bei der Ausstellung nicht bestanden; unavailable: es konnte kein Token bezogen werden; time_budget: die Anfrage passte nicht in die verbleibende Zeit, wurde daher nicht gestellt, vom Zeitstempeldienst vor jeder Anfrage an die Stelle abgelehnt oder abgebrochen, was auf der synchronen Schwärzungsroute oder am Ende einer langen Aufnahme vorkommt; account_limit: das Konto hat noch keine bezahlte Rechnung und hat sein eIDAS-Limit von 20 Stempeln pro UTC-Tag oder 50 pro UTC-Monat erreicht, gezählt über Aufnahmen, Batches, Zeitpläne, Zertifizierungen, Vergleiche, Schwärzungen und Mandatsversiegelungen zusammen, daher wurde keine Anfrage gestellt; Konten mit einer bezahlten Rechnung sind nicht begrenzt). Vor dem 2026-10-01 abgeschlossene Zeilen melden nur confirmed.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "time_budget",
              "account_limit"
            ],
            "description": "Nur bei not_obtained."
          },
          "serial": {
            "type": "string",
            "nullable": true,
            "description": "Nur bei confirmed."
          },
          "issued_at": {
            "type": "string",
            "nullable": true,
            "description": "Nur bei confirmed."
          },
          "revocation": {
            "type": "string",
            "enum": [
              "unknown"
            ],
            "description": "Nur bei confirmed und nur, wenn der Status des TSA-Zertifikats bei der Ausstellung nicht festgestellt werden konnte (das Token hat jede andere Prüfung bestanden). Das Token wird ausgeliefert und nicht abgerechnet."
          },
          "billed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Vorhanden (false) zusammen mit revocation unknown: Dieses Token wurde nicht abgerechnet."
          }
        }
      },
      "ExternalArchive": {
        "type": "object",
        "description": "Das Ergebnis des Internet Archive (Wayback Machine) zu einer Aufnahme. Das Internet Archive ist ein öffentliches Archiv eines Drittanbieters.",
        "required": [
          "requested"
        ],
        "properties": {
          "requested": {
            "type": "boolean",
            "description": "false, wenn keine Anfrage an das Internet Archive gestellt wurde (external_archive: false, eine Zertifizierung oder ein Vergleich, eine Aufnahme aus der Zeit vor der Archivierung, oder die Anfrage passte nicht in das Zeitbudget). Alle anderen Felder fehlen dann."
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "Aktuelles Ergebnis. Bei einem Save-Page-Now-Job, der bei der Versiegelung noch ausstand, liest der Scheduler den Jobstatus etwa 5 und 30 Minuten nach der Anfrage, und dies wird zum endgültigen Ergebnis (created, recent_existing, failed, rate_limited oder unconfirmed, wenn innerhalb des Prüfzeitfensters keine Antwort kam). created, recent_existing, created_after_request und existing enthalten snapshot_url."
          },
          "status_at_sealing": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "Der im Paket versiegelte Zustand (metadata.json, forensic_log.json, chain_of_custody.json und evidence.pdf erfassen ihn). Das Paket selbst ändert sich nie; entspricht status, sofern nicht eine Nachverfolgung einen ausstehenden Job aufgelöst hat."
          },
          "method": {
            "type": "string",
            "nullable": true,
            "enum": [
              "spn2_authenticated",
              "spn_anonymous",
              null
            ]
          },
          "requested_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "snapshot_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Die öffentliche Kopie in der Wayback Machine, stets https://web.archive.org/web/{snapshot_timestamp}/{url}."
          },
          "snapshot_timestamp": {
            "type": "string",
            "nullable": true,
            "description": "Wayback-Zeitstempel YYYYMMDDhhmmss (UTC)."
          },
          "snapshot_offset_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "Snapshot-Zeit minus Aufnahmezeit, in Sekunden (negativ: vor der Aufnahme)."
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Zeitpunkt, zu dem die Nachverfolgung des Schedulers das endgültige Ergebnis erfasst hat; null, wenn der versiegelte Zustand endgültig war."
          },
          "job_id": {
            "type": "string",
            "nullable": true,
            "description": "Save-Page-Now-Job-ID (authentifizierte Anfragen)."
          },
          "follow_up_pending": {
            "type": "boolean",
            "description": "true, solange der Scheduler den Jobstatus noch lesen muss."
          },
          "skip_reason": {
            "type": "string",
            "nullable": true,
            "description": "Warum die URL nicht gesendet wurde, wenn status skipped, skipped_sensitive oder skipped_query ist: credentials_in_url, sensitive_path, opaque_path_segment, sensitive_fragment, sensitive_parameter_name, opaque_parameter_value, query_string (oder bei älteren Zeilen der Status selbst)."
          },
          "resolved": {
            "type": "object",
            "nullable": true,
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "recent_existing",
                  "failed",
                  "rate_limited",
                  "unresolved"
                ]
              },
              "job_status": {
                "type": "string",
                "nullable": true,
                "description": "success, error oder pending, wie aus dem Save-Page-Now-Job gelesen, null, wenn er nicht gelesen werden konnte."
              },
              "snapshot_url": {
                "type": "string",
                "nullable": true,
                "description": "https://web.archive.org/web/<14 digits>/<URL>, null, wenn nicht vorhanden oder ungültig."
              },
              "snapshot_timestamp": {
                "type": "string",
                "nullable": true
              },
              "checked_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "no_request_time",
                  "capture_deleted",
                  "capture_failed",
                  null
                ],
                "description": "Warum keine Jobantwort vorliegt: no_request_time (die Zeile hat keinen Anfragezeitpunkt, von dem aus die Abrufe geplant werden könnten), capture_deleted oder capture_failed (die Aufnahme wurde in den Papierkorb verschoben oder ist fehlgeschlagen, während der Job ausstand, und wurde daher nicht nachverfolgt; eine Aufnahme, die innerhalb von 30 Minuten nach der Anfrage aus dem Papierkorb wiederhergestellt wird, wird erneut nachverfolgt). Sonst null."
              }
            },
            "description": "Der Nachverfolgungsdatensatz (Verlauf) als feste Projektion: snapshot_url nur, wenn es sich um eine gültige Wayback-Snapshot-URL zu snapshot_timestamp handelt; reason nur, wenn die Nachverfolgung ohne Jobantwort endete."
          }
        }
      },
      "CaptureStorage": {
        "type": "object",
        "description": "Aufbewahrung, die Duplikatkopie und (bei automatischen Aufnahmen) der Versionsverlauf.",
        "properties": {
          "retention": {
            "type": "object",
            "properties": {
              "years": {
                "type": "integer",
                "example": 10
              },
              "counted_from": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Der spätere Zeitpunkt von created_at und der letzten bekannten Nutzung: die letzte Prüfung, die die Seite unverändert vorfand, oder der Moment, in dem eine neuere Aufnahme desselben Zeitplans diese ersetzt hat."
              },
              "earliest_deletion_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "counted_from plus years, ungefähr: Die Löschung erfolgt bei einem späteren Durchlauf des Schedulers, und spätere Prüfungen können den Zeitpunkt verschieben. null bei einer Löschsperre."
              },
              "legal_hold": {
                "type": "boolean"
              }
            }
          },
          "duplicate_copy": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "pending",
                  "stored",
                  "not_copied"
                ],
                "description": "stored: Eine zweite Kopie des Pakets (und seines aktualisierten OTS-Nachweises) wird in einem separaten Bucket in einer anderen EU-Region aufbewahrt und mit dem Original gelöscht. pending: wird kopiert (Minuten). none: Testpakete sowie Pakete aus der Zeit vor Einführung des Duplikats, bis sie kopiert sind."
              },
              "copied_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "version": {
            "type": "object",
            "description": "Nur bei automatischen Aufnahmen.",
            "properties": {
              "superseded_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "superseded_by": {
                "type": "string",
                "nullable": true
              },
              "unchanged_first_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Erste Prüfung (on_change), die den Seitentext seit dieser Aufnahme unverändert vorfand."
              },
              "unchanged_last_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "unchanged_checks": {
                "type": "integer",
                "description": "Erfasste Prüfungen ohne Änderung (höchstens eine pro Stunde)."
              }
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "capture.completed",
          "capture.failed",
          "certify.completed",
          "certify.failed",
          "redaction.completed",
          "redaction.failed",
          "monitor.change_detected",
          "capture.anchor_confirmed",
          "schedule.paused"
        ],
        "description": "Ein Ereignis aus dem Katalog (Tag Webhooks)."
      },
      "WebhookSubscriptionFilters": {
        "type": "object",
        "description": "Höchstens eines von host und schedule_id. Das Abonnement erhält dann nur die Ereignisse von Zeilen mit diesem Host (Hostname der aufgenommenen URL in Kleinbuchstaben) oder dieses Zeitplans. `{}` für keinen Filter.",
        "properties": {
          "host": {
            "type": "string",
            "example": "www.example.com"
          },
          "schedule_id": {
            "type": "string",
            "example": "sch_a1B2c3D4e5F6g7H8"
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "https-Endpunkt, an den die Ereignisse per POST gesendet werden."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Die Umgebung der Anmeldedaten, mit denen es erstellt wurde. Ein Live-Abonnement erhält nur die Ereignisse von Live-Zeilen, ein Test-Abonnement nur die von Test-Zeilen."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: mit einem API-Schlüssel erstellt; der Widerruf dieses Schlüssels deaktiviert das Abonnement (disabled_reason key_revoked)."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Fehlgeschlagene Zustellungen seit der letzten erfolgreichen."
          },
          "last_success_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_failure_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_error": {
            "type": "string",
            "nullable": true,
            "description": "Fehlerklasse der letzten fehlgeschlagenen Zustellung (timeout, connection_failed, blocked_address, redirect_not_followed, http_<status>)."
          },
          "disabled_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "gone",
              "failing",
              "key_revoked",
              null
            ],
            "description": "gone: Der Endpunkt hat mit 410 geantwortet. failing: Jede Zustellung ist 5 Tage lang ohne Erfolg fehlgeschlagen. key_revoked: Der API-Schlüssel, mit dem es erstellt wurde, wurde widerrufen. PATCH status active reaktiviert es."
          },
          "disabled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "consecutive_failures",
          "last_success_at",
          "last_failure_at",
          "last_error",
          "disabled_reason",
          "disabled_at"
        ]
      },
      "WebhookSubscriptionCreated": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "https-Endpunkt, an den die Ereignisse per POST gesendet werden."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Die Umgebung der Anmeldedaten, mit denen es erstellt wurde. Ein Live-Abonnement erhält nur die Ereignisse von Live-Zeilen, ein Test-Abonnement nur die von Test-Zeilen."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: mit einem API-Schlüssel erstellt; der Widerruf dieses Schlüssels deaktiviert das Abonnement (disabled_reason key_revoked)."
          },
          "secret": {
            "type": "string",
            "description": "whsec_ gefolgt von Standard-Base64. Wird EINMAL angezeigt: Jede Zustellung an dieses Abonnement wird damit signiert (Standard Webhooks). Speichern Sie es jetzt; es kann nicht erneut abgerufen werden. Eine Wiederholung mit Idempotency-Key innerhalb von 24 Stunden liefert denselben Body, einschließlich Secret."
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "secret"
        ]
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_a1B2c3D4e5F6g7H8"
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "data": {
            "type": "object",
            "description": "Der Ereignis-Body, wie Abonnements ihn erhalten, ohne subscription_id und ohne vorsignierte Links (package_url, package_url_expires_at, upgraded_proof_url, compare.package_url)."
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "env",
          "data"
        ]
      },
      "PrivateNote": {
        "type": "string",
        "nullable": true,
        "maxLength": 200,
        "description": "Ihre private Notiz zur Aufnahme (zum Beispiel „mobile Aufnahme“), höchstens 200 Zeichen, reiner Text (die PlainText-Regel). Sie wird nur im Aufnahmedatensatz gespeichert: Sie ist NICHT Teil des versiegelten Beweispakets (nicht in der ZIP, im PDF, im Manifest oder in einem Hash) und hat keine Beweiskraft, sodass sie später mit PATCH /v1/captures/{capture_id} geändert oder gelöscht werden kann, ohne die Beweise zu beeinflussen. Nur der Ersteller der Aufnahme kann sie lesen. Lassen Sie sie weg oder senden Sie null oder eine leere Zeichenkette, wenn Sie keine Notiz wünschen.",
        "example": "mobile capture"
      },
      "CaptureEvent": {
        "type": "object",
        "description": "Ein Ereignis der Aufnahme-Ereigniskette des Kontos. hash = SHA-256 (hex) des kanonischen JSON der gehashten Felder (v, chain, seq, type, capture_id, manifest_sha256, actor, access, detail, at, prev_hash; Schlüssel sortiert, ohne Leerraum, fehlende Werte als null). prev_hash ist der Hash des vorherigen Ereignisses, 64 Nullen bei seq 1.",
        "properties": {
          "v": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "chain": {
            "type": "string",
            "description": "Das Konto: die uid bei persönlichen Aufnahmen, org_<org_id> bei Aufnahmen eines Unternehmens."
          },
          "seq": {
            "type": "integer",
            "minimum": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "trash",
              "restore",
              "erase",
              "purge",
              "retention_deletion",
              "account_deletion",
              "company_deletion",
              "legal_hold_on",
              "legal_hold_off",
              "s3_legal_hold",
              "import"
            ]
          },
          "capture_id": {
            "type": "string"
          },
          "manifest_sha256": {
            "type": "string",
            "nullable": true,
            "description": "SHA-256 der manifest.json der Aufnahme, vor jeder Löschung gelesen."
          },
          "actor": {
            "type": "string",
            "description": "Die uid des handelnden Kontos oder system bei Durchläufen des Servers."
          },
          "access": {
            "type": "string",
            "description": "author, company_owner, matter_owner oder system."
          },
          "detail": {
            "type": "string",
            "nullable": true,
            "description": "legal_hold_on / legal_hold_off: die Quelle der Löschsperre (manual, bulk, matter:<id>, derived:<capture_id>) oder bei einer Aufhebung durch das System (actor system) ihr Grundcode subscription_ended; s3_legal_hold: das S3-Ergebnis (on, off, failed, unsupported, none); import: die Art des importierten Pakets. Kein Freitext."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "Zeitpunkt, zu dem das Ereignis in die Kette geschrieben wurde. Nie früher als das at des vorherigen Ereignisses."
          },
          "prev_hash": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "Zeitpunkt, zu dem die Aktion selbst festgeschrieben wurde (die Transaktion der Zustandsänderung). Nicht gehasht. Normalerweise innerhalb von Millisekunden gleich at; nur später, wenn der Server das Anhängen an die Kette wiederholt hat (die Aktion wird in derselben Transaktion wie die Änderung erfasst, daher geht keine verloren)."
          }
        }
      },
      "CaptureEventAnchor": {
        "type": "object",
        "description": "Eine tägliche OpenTimestamps-Verankerung der Kette: head_hash ist der Hash des Ereignisses to_seq, der alle früheren Ereignisse kryptografisch einschließt.",
        "properties": {
          "anchor_id": {
            "type": "string"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "from_seq": {
            "type": "integer"
          },
          "to_seq": {
            "type": "integer"
          },
          "head_hash": {
            "type": "string"
          },
          "ots_proof_base64": {
            "type": "string",
            "nullable": true,
            "description": "Der OpenTimestamps-Nachweis (.ots-Datei, Base64) über head_hash."
          },
          "ots_status": {
            "type": "string",
            "enum": [
              "pending",
              "bitcoin",
              "unconfirmed"
            ],
            "description": "pending: ots_proof_base64 enthält die Zusage der Kalender (ots verify fragt die Kalender ab); bitcoin: sie wurde durch den auf den Bitcoin-Block aktualisierten Nachweis ersetzt (bitcoin_block_height); unconfirmed: Innerhalb von 14 Tagen wurde kein Bitcoin-Nachweis geliefert, der gespeicherte Nachweis lässt sich weiterhin über die Kalender prüfen."
          },
          "bitcoin_block_height": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ImportSummary": {
        "type": "object",
        "description": "Die drei Ebenen eines verifizierten Pakets.",
        "properties": {
          "kind_label_key": {
            "type": "string",
            "description": "Bezeichnungsschlüssel der Paketart, import_kind_<kind>."
          },
          "integrity": {
            "type": "string",
            "enum": [
              "verified",
              "failed"
            ]
          },
          "time_anchor": {
            "type": "string",
            "enum": [
              "eidas_qualified",
              "eidas_qualified_revocation_unknown",
              "ots_bitcoin_attested_unconfirmed",
              "ots_pending",
              "none"
            ]
          },
          "provenance": {
            "type": "string",
            "nullable": true,
            "enum": [
              "issued_by_proofsnap",
              "unverified",
              "registered_by_extension_account",
              "not_registered",
              "unknown",
              null
            ]
          },
          "registered_at": {
            "type": "string",
            "format": "date-time",
            "description": "Zeitpunkt, zu dem das Register der Erweiterung das Paket erfasst hat (provenance registered_by_extension_account)."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "Zeitpunkt, zu dem das API-Register die Ausstellung erfasst hat (provenance issued_by_proofsnap)."
          }
        }
      },
      "Import": {
        "type": "object",
        "properties": {
          "import_id": {
            "type": "string",
            "example": "imp_a1B2c3D4e5F6g7H8i9J0"
          },
          "status": {
            "type": "string",
            "enum": [
              "uploading",
              "verifying",
              "accepted",
              "rejected",
              "duplicate",
              "expired"
            ]
          },
          "filename": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "upload_expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Nur bei uploading: vor diesem Zeitpunkt abschließen (24 Stunden nach der Erstellung)."
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "description": "Zeitpunkt, zu dem der Import seinen endgültigen Status erreicht hat."
          },
          "capture_id": {
            "type": "string",
            "description": "Nur bei accepted: die neue Aufnahme (dieselbe ID wie der Import)."
          },
          "duplicate_of": {
            "type": "string",
            "description": "Nur bei duplicate: Ihre Aufnahme, die dieses Manifest bereits enthält."
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ],
            "description": "Die Paketart, sobald erkannt."
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "rejected und expired: Grundcodes, zum Beispiel file_mismatch, file_missing, unlisted_file, bad_signature, no_hashed_files, unknown_schema, zip_too_large, test_package, ots_digest_mismatch, eidas_token_failed, forensic_chain_broken, size_mismatch, upload_not_completed, verification_incomplete. Nur Codes, nie Dateinamen."
          },
          "zip_sha256": {
            "type": "string",
            "description": "SHA-256 der hochgeladenen ZIP, sobald berechnet."
          },
          "manifest_sha256": {
            "type": "string",
            "description": "SHA-256 der manifest.json des Pakets (accepted und duplicate)."
          },
          "matter_id": {
            "type": "string",
            "description": "Das bei der Erstellung des Imports angegebene Mandat."
          },
          "matter_assignment": {
            "type": "string",
            "enum": [
              "assigned",
              "skipped"
            ],
            "description": "accepted mit matter_id: ob die Aufnahme dem Mandat hinzugefügt wurde (übersprungen, wenn das Mandat bis dahin versiegelt, archiviert oder für Sie nicht mehr offen war; der Import bleibt accepted)."
          },
          "upload": {
            "type": "object",
            "description": "Nur mit ?upload=true, solange status uploading ist.",
            "properties": {
              "upload_id": {
                "type": "string"
              },
              "part_size": {
                "type": "integer"
              },
              "parts": {
                "type": "array",
                "description": "Vorsignierte PUT-URLs der noch nicht empfangenen Teile; per PUT genau size_bytes senden.",
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "expires_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "received": {
                "type": "array",
                "nullable": true,
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "etag": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "completed": {
                "type": "boolean",
                "description": "Der Upload wurde bereits abgeschlossen: Senden Sie complete erneut."
              }
            }
          }
        }
      },
      "ImportVerification": {
        "type": "object",
        "description": "Der beim Import erfasste Prüfbericht (keine Dateiinhalte, keine Freitextdetails).",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "format": {
            "type": "string"
          },
          "kind_label_key": {
            "type": "string"
          },
          "evidence_id": {
            "type": "string",
            "description": "Die ID, die das Paket trägt (ps_..., FC-..., cap_..., cert_..., cmp_..., red_...)."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "anchor_sha256": {
            "type": "string",
            "description": "Der Hash, den die Zeitstempel abdecken: der des Manifests oder bei einer Einzeldatei-Zertifizierung der der zertifizierten Datei."
          },
          "anchor_source": {
            "type": "string",
            "enum": [
              "manifest",
              "certified_file"
            ]
          },
          "checks": {
            "type": "object",
            "description": "structure, signature, files (listed, verified, presence_only, unhashed, os_junk_ignored), custody, forensic_chain, ots, eidas, test_mode.",
            "additionalProperties": true
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CaptureImport": {
        "type": "object",
        "description": "Nur bei einer Aufnahme mit source \"import\".",
        "properties": {
          "import_id": {
            "type": "string"
          },
          "package_kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "kind_label_key": {
            "type": "string"
          },
          "package_format": {
            "type": "string"
          },
          "original_evidence_id": {
            "type": "string"
          },
          "original_filename": {
            "type": "string",
            "nullable": true
          },
          "imported_at": {
            "type": "string",
            "format": "date-time"
          },
          "captured_at_basis": {
            "type": "string",
            "enum": [
              "package_claim"
            ],
            "description": "captured_at eines importierten Pakets ist der im Paket angegebene Zeitpunkt, nie ein von ProofSnap beobachteter Zeitpunkt."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "levels": {
            "type": "object",
            "properties": {
              "integrity": {
                "type": "string"
              },
              "time_anchor": {
                "type": "string"
              },
              "provenance": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "verification": {
            "$ref": "#/components/schemas/ImportVerification"
          },
          "provenance": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "api_ledger",
                  "extension_registry",
                  null
                ]
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "registered_at": {
                "type": "string",
                "nullable": true
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      },
      "Rule902DisabledWarnings": {
        "type": "array",
        "description": "Nur vorhanden, wenn die Anfrage die Zertifizierungsvorlage angefordert hat (include_rule_902: true oder bei einer Aufnahme ein nicht leeres evidence_pack.custodian), während die Vorlage deaktiviert ist. Die Anfrage wurde angenommen und das Flag ignoriert: Es wird keine rule_902_certification.pdf erzeugt.",
        "items": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string",
              "enum": [
                "RULE902_DISABLED"
              ]
            },
            "message": {
              "type": "string",
              "example": "The FRE 902 certification template (rule_902_certification.pdf) is currently disabled. include_rule_902 was accepted and ignored: the package does not contain the template."
            }
          },
          "required": [
            "code",
            "message"
          ]
        }
      }
    }
  }
}
