{
  "openapi": "3.1.0",
  "info": {
    "title": "Koivu Screening API",
    "version": "2026-10-15",
    "summary": "Sanktions-, PEP- und Adverse-Media-Signale mit Beweis.",
    "description": "Die Koivu Screening API liefert **Signale, nie Entscheide**. Jede Antwort trägt `decision: \"signal_only\"`;\ndie Entscheidung über einen Treffer bleibt bei Ihnen. Kein Treffer ist keine Freigabe.\n\n## In fünf Minuten\n1. Schlüssel erhalten (`sk_test_…` zum Ausprobieren, `sk_live_…` für den Betrieb).\n2. `POST /v1/match` mit einem Namen aufrufen, Header `Authorization: Bearer <Schlüssel>`.\n3. `confidence_band` lesen: `high` = starkes Signal, `review` = prüfen. `explain` zeigt, warum.\n4. Bestände (Listen) über `POST /v1/upload` oder `POST /v1/batch` prüfen, Ergebnis über `/v1/batch/{job_id}/summary` und `/positives`.\n5. Dauerhaft überwachen: `POST /v1/monitors`.\n\nAusführliche Anleitung mit curl und Python: Quickstart (`quickstart.md`).\n\n## Begriffe\n* **Signal:** ein Hinweis, dass eine Person oder Organisation auf einer Liste steht oder dort sehr ähnlich geführt wird. Ein Signal ist nie der Entscheid.\n* **Bänder:** `high` (Name und Identitätsmerkmale stimmen überein) und `review` (Ähnlichkeit, zu prüfen). Unter `review` liegende Hinweise erscheinen nicht als Alarm.\n* **`suppressed_hits`:** Hinweise, die wir bewusst nicht als Alarm zählen (z. B. nur Namensähnlichkeit ohne Bindung an die Person). Sie sind sichtbar, damit nichts verborgen bleibt.\n* **Quellen und Nachweise:** Jeder Treffer nennt Quelle, Listenstand und Abrufzeit. Zu jeder Einzelprüfung gibt es einen signierten Nachweis (`/v1/evidence/{request_id}`).\n* **`checked_against`:** Liste der Quellen, gegen die geprüft wurde, mit Stand.\n\n## Authentifizierung\nJede Anfrage trägt den Schlüssel als `Authorization: Bearer sk_live_…` (oder `sk_test_…`). Cookies und andere Header gelten nicht.\nSchlüssel sind je Kundin und Zweck getrennt, können einen Ablauf haben und optional auf IP-Adressen eingeschränkt sein.\nDer Klartext wird nur einmal bei der Ausgabe gezeigt; wir speichern nur einen Hash. Rotation mit Überlappung (alter und neuer Schlüssel gelten\nparallel) und Widerruf veranlassen Sie bei uns. Fehlt der Schlüssel oder ist er widerrufen oder abgelaufen: `401`. Fehlt der Scope oder ist die Quell-IP nicht erlaubt: `403`.\n`sk_test_…`-Schlüssel haben engere Limits und markieren Antworten mit `Koivu-Environment: test`.\n\n## Fehler\nSenden Sie `Koivu-Version: 2026-10-15`, antworten alle Fehler als `application/problem+json` (RFC 9457) mit stabilem `code`, `request_id` und bei\nEingabefehlern `errors[]` (JSON-Pointer). Ohne diesen Header bleibt das bisherige Format `{\"detail\": …}`. Nennen Sie bei Rückfragen die `request_id`\n(auch im Header `X-Request-ID`). Der Fehlerkatalog steht unter `info.x-koivu-problem-catalog`.\n\n## Wiederholbare Aufrufe\n`POST /v1/match` und `POST /v1/batch` akzeptieren `Idempotency-Key` (bis 255 Zeichen, 24 Stunden, je Schlüssel). Dieselbe Anfrage mit demselben Key liefert\ndie gespeicherte Antwort (Header `Idempotent-Replayed: true`) und zählt nicht erneut; derselbe Key mit anderem Inhalt: `422 idempotency_key_reused`;\nläuft die erste Anfrage noch: `409 idempotency_in_progress`. Fehlerantworten werden nicht gespeichert.\n\n## Limits\nJe Schlüssel gilt ein Minutenlimit und ein Tageskontingent (Vorgabe: 120 Anfragen pro Minute, vertraglich anpassbar). Jede Antwort trägt `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` und `X-RateLimit-Reset`. Bei `429` gilt `Retry-After`. `POST /v1/match` zählt je Query, `POST /v1/batch` je Zeile.\nGrössen: `POST /v1/match` bis 512 KB, `POST /v1/batch` bis 32 MB, `POST /v1/upload` bis 50 MB.\n\n## Versionierung\nDer Pfad `/v1` ist die Hauptversion. Bestehende Felder und Pfade ändern sich nicht; neue Felder kommen additiv hinzu. Clients ignorieren unbekannte Felder.\n",
    "contact": {
      "name": "Koivu"
    },
    "x-koivu-problem-catalog": [
      {
        "code": "validation_failed",
        "status": 422,
        "title": "Eingabe ungültig"
      },
      {
        "code": "unauthorized",
        "status": 401,
        "title": "Schlüssel fehlt, ist ungültig, widerrufen oder abgelaufen"
      },
      {
        "code": "forbidden_scope",
        "status": 403,
        "title": "Der Schlüssel hat den Scope nicht"
      },
      {
        "code": "ip_not_allowed",
        "status": 403,
        "title": "Zugriff von dieser IP-Adresse nicht erlaubt"
      },
      {
        "code": "forbidden",
        "status": 403,
        "title": "Pfad nicht öffentlich"
      },
      {
        "code": "not_found",
        "status": 404,
        "title": "Ressource nicht gefunden"
      },
      {
        "code": "method_not_allowed",
        "status": 405,
        "title": "Methode nicht erlaubt"
      },
      {
        "code": "conflict",
        "status": 409,
        "title": "Konflikt (z. B. veralteter Stand)"
      },
      {
        "code": "idempotency_in_progress",
        "status": 409,
        "title": "Gleicher Idempotency-Key läuft noch"
      },
      {
        "code": "idempotency_key_reused",
        "status": 422,
        "title": "Idempotency-Key mit anderen Parametern"
      },
      {
        "code": "payload_too_large",
        "status": 413,
        "title": "Nutzlast zu gross"
      },
      {
        "code": "unsupported_format",
        "status": 415,
        "title": "Format nicht unterstützt"
      },
      {
        "code": "rate_limited",
        "status": 429,
        "title": "Minutenlimit erreicht"
      },
      {
        "code": "quota_exceeded",
        "status": 429,
        "title": "Tageskontingent erreicht"
      },
      {
        "code": "list_stale_mandatory",
        "status": 503,
        "title": "Pflichtliste nicht frisch"
      },
      {
        "code": "database_unavailable",
        "status": 503,
        "title": "Datenbank nicht erreichbar"
      },
      {
        "code": "service_unavailable",
        "status": 503,
        "title": "Dienst vorübergehend nicht verfügbar"
      },
      {
        "code": "internal_error",
        "status": 500,
        "title": "Interner Fehler"
      }
    ]
  },
  "servers": [
    {
      "url": "{base_url}",
      "description": "Basis-URL der API; wir teilen sie Ihnen mit dem Schlüssel mit.",
      "variables": {
        "base_url": {
          "default": "https://api.example.com"
        }
      }
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Screenings",
      "description": "Einzelprüfung (`/v1/match`)."
    },
    {
      "name": "Batches",
      "description": "Bestand prüfen, Ergebnisse, Delta, Export."
    },
    {
      "name": "Sources",
      "description": "Quellen und Listenstand."
    },
    {
      "name": "Evidence",
      "description": "Signierte Nachweise und Berichte."
    },
    {
      "name": "Monitoring",
      "description": "Dauerüberwachung (Monitore), Fälle und Webhooks."
    },
    {
      "name": "Account",
      "description": "Konto, Plan, Scopes und Nutzung."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Prüfen",
      "tags": [
        "Screenings",
        "Batches"
      ]
    },
    {
      "name": "Belege",
      "tags": [
        "Evidence",
        "Sources"
      ]
    },
    {
      "name": "Überwachung und Konto",
      "tags": [
        "Monitoring",
        "Account"
      ]
    }
  ],
  "paths": {
    "/v1/match": {
      "post": {
        "operationId": "matchScreening",
        "tags": [
          "Screenings"
        ],
        "summary": "Eine oder mehrere Personen/Organisationen prüfen (synchron)",
        "description": "Prüft gegen alle aktiven Quellen (Sanktionen, PEP, Enforcement, Adverse-Media-Signale). Eingabe in der\nOpenSanctions-nahen Form (`queries` mit `properties`) **oder** in der Kurzform (`name`, `birth_year`, `country`).\nBis zu 50 `queries` je Aufruf; jede wird für das Kontingent gewichtet. Scope `match`.\n\nDie Antwort liefert je Query `responses.<id>.results[]` zusammengeführte Personen mit `confidence_band` (`high` oder `review`),\n`explain` (Gründe mit Teilwerten), `source` und `records`. Ausgeblendete Hinweise stehen in `suppressed_hits`\n(nicht als Alarm gezählt). `checked_against` nennt Quellen und Listenstand, `evidence` ist der signierte Nachweis.\n\n**Haltung:** `decision` ist immer `signal_only`, `true_match` immer `false`. 0 Treffer ist **keine Freigabe**.\nMit dem Header `Idempotency-Key` ist der Aufruf wiederholbar, ohne doppelt zu zählen.",
        "parameters": [
          {
            "name": "threshold",
            "in": "query",
            "description": "Mindest-Score 0,5 bis 1,0 (Body hat Vorrang). Standard 0,85.",
            "schema": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 1.0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Treffer je Query, 1 bis 50 (Body hat Vorrang). Sanktions-Alarme werden nie durch `limit` gekappt.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "include_deceased",
            "in": "query",
            "description": "Verstorbene PEP/RCA als Treffer ausliefern (soft, `band_reason=deceased`).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "alert_policy",
            "in": "query",
            "description": "Alarm-Regeln je Quellenart: `standard` (Default), `strict`, `off`.",
            "schema": {
              "$ref": "#/components/schemas/AlertPolicy"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchRequest"
              },
              "examples": {
                "kurzform": {
                  "summary": "Kurzform mit Geburtsjahr und Land",
                  "value": {
                    "name": "Vladimir Putin",
                    "birth_year": 1952,
                    "country": "RU",
                    "limit": 5
                  }
                },
                "queries": {
                  "summary": "Mehrere Queries (OpenSanctions-nahe Form)",
                  "value": {
                    "queries": {
                      "q1": {
                        "schema": "Person",
                        "properties": {
                          "name": [
                            "Bernard Madoff"
                          ],
                          "birthDate": [
                            "1938"
                          ]
                        }
                      },
                      "q2": {
                        "schema": "Person",
                        "properties": {
                          "name": [
                            "Erika Mustermann"
                          ],
                          "country": [
                            "DE"
                          ]
                        }
                      }
                    },
                    "threshold": 0.85
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ergebnis je Query. Auch ohne Treffer 200 (dann `results: []`).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchResponse"
                },
                "examples": {
                  "mit_treffer": {
                    "summary": "Beispielantwort (gekürzt)",
                    "value": {
                      "responses": {
                        "q1": {
                          "status": 200,
                          "results": [
                            {
                              "id": "ch-seco-49812",
                              "entity_id": "ch-seco-49812",
                              "caption": "Vladimir Putin",
                              "caption_original": null,
                              "score": 1.0,
                              "confidence_band": "high",
                              "explain": {
                                "name": {
                                  "query": "Vladimir Putin",
                                  "listed": "Vladimir Putin",
                                  "matched_via": "primary",
                                  "kind": "exact",
                                  "score": 1.0,
                                  "tokens": {
                                    "common": [
                                      "vladimir",
                                      "putin"
                                    ],
                                    "query_only": [],
                                    "listed_only": []
                                  }
                                },
                                "birth": {
                                  "query": 1952,
                                  "listed": "1952-10-07",
                                  "listed_years": [
                                    1952
                                  ],
                                  "result": "confirmed"
                                },
                                "country": {
                                  "query": "RU",
                                  "listed": [
                                    "RU"
                                  ],
                                  "result": "confirmed"
                                },
                                "id_number": {
                                  "result": "not_comparable"
                                },
                                "identity": "confirmed",
                                "band_reason": "name_exact_identity_confirmed",
                                "policy": {
                                  "class": "sanctions",
                                  "mode": "standard",
                                  "action": "keep"
                                }
                              },
                              "source": {
                                "id": "ch_seco",
                                "label": "SECO Sanktionsgesamtliste (SESAM)",
                                "country": "CH",
                                "category": "sanctions",
                                "list_date": "2026-10-07T08:00:09.759482+00:00",
                                "list_date_basis": "fetched",
                                "url": "https://www.sesam.search.admin.ch/sesam-search-web/pages/search.xhtml",
                                "url_kind": "list"
                              },
                              "records": [
                                {
                                  "source_id": "ch_seco",
                                  "entity_id": "ch-seco-49812",
                                  "list_version": "ch-5f1315863aed",
                                  "url": null
                                },
                                {
                                  "source_id": "jp_mof",
                                  "entity_id": "jp-mof-029-000001",
                                  "list_version": "jp-a20b1b4a428e",
                                  "url": null
                                },
                                {
                                  "source_id": "ofac_sdn",
                                  "entity_id": "ofac-sdn-35096",
                                  "list_version": "ofac-b54e8f5535cd",
                                  "url": "https://sanctionssearch.ofac.treas.gov/Details.aspx?id=35096"
                                }
                              ],
                              "deceased": null,
                              "death_date": null,
                              "is_rca": false,
                              "features": {
                                "birth_date": "1952-10-07",
                                "birth_year": 1952,
                                "countries": [
                                  "RU"
                                ],
                                "nationality": [],
                                "aliases": [
                                  "Putin Vladimir",
                                  "Vladimir Vladimirovich Putin"
                                ]
                              },
                              "match": true,
                              "true_match": false,
                              "score_adjusted": 1.0
                            }
                          ],
                          "suppressed_hits": [],
                          "hit_count": 4,
                          "record_count": 14,
                          "suppressed": {
                            "deceased_pep": 0,
                            "phonetic_only": 0,
                            "over_limit": 1,
                            "unconfirmed_similar": 1
                          },
                          "more_available": true,
                          "alert_policy": "standard",
                          "total": {
                            "value": 4,
                            "relation": "eq"
                          },
                          "query": {
                            "name": "Vladimir Putin",
                            "schema": null,
                            "short_name": false,
                            "effective_threshold": 0.85,
                            "requested_threshold": 0.85,
                            "matcher_backend": "rapidfuzz"
                          }
                        }
                      },
                      "matcher": "mvp-rapidfuzz-v2",
                      "matcher_backend": "rapidfuzz",
                      "config_hash": "ee075db024bb7c71",
                      "stale": false,
                      "checked_against": {
                        "sources": 294,
                        "mandatory": [
                          {
                            "id": "ch_seco",
                            "label": "SECO Sanktionsgesamtliste (SESAM)",
                            "list_date": "2026-10-07T08:00:09.759482+00:00",
                            "list_date_basis": "fetched"
                          },
                          {
                            "id": "eu_fsf",
                            "label": "EU Financial Sanctions Files (FSF)",
                            "list_date": "2026-10-07T07:25:55.337128+00:00",
                            "list_date_basis": "fetched"
                          }
                        ],
                        "oldest_list_date": "2026-10-07T07:25:55.337128+00:00"
                      },
                      "suppressed": {
                        "over_limit": 1,
                        "unconfirmed_similar": 1
                      },
                      "alert_policy": "standard",
                      "threshold": 0.85,
                      "limit": 2,
                      "request_id": "34cfaf79-3cf1-4f8e-9c6a-55723dd979d1",
                      "audited_at": "2026-10-07T08:56:58.412326+00:00",
                      "confidence_bands": {
                        "soft": "0.75-0.89",
                        "review": "0.90-0.97",
                        "high": ">=0.98",
                        "auto_true_match": false
                      },
                      "decision": "signal_only",
                      "true_match": false
                    }
                  },
                  "ohne_treffer": {
                    "summary": "Kein Treffer. Das ist ein Befund über Listenstand, keine Freigabe.",
                    "value": {
                      "responses": {
                        "q1": {
                          "status": 200,
                          "results": [],
                          "suppressed_hits": [],
                          "hit_count": 0,
                          "record_count": 0,
                          "suppressed": {},
                          "more_available": false,
                          "alert_policy": "standard",
                          "total": {
                            "value": 0,
                            "relation": "eq"
                          }
                        }
                      },
                      "checked_against": {
                        "sources": 93,
                        "mandatory": [],
                        "oldest_list_date": "2026-10-05T03:00:00Z"
                      },
                      "request_id": "3b0e1c64-8f55-4c1d-9a2f-0a77d1b9c2e4",
                      "decision": "signal_only",
                      "true_match": false
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/match\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Vladimir Putin\", \"birth_year\": 1952, \"country\": \"RU\", \"limit\": 5}'"
          }
        ]
      }
    },
    "/v1/batch": {
      "post": {
        "operationId": "createBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Bestand prüfen (inline, bis 5000 Zeilen)",
        "description": "Prüft eine Liste von Personen. Bis 50 Zeilen laufen synchron (`mode: sync`, Ergebnis in `responses`),\nmehr oder mit `async_mode: true` asynchron (`mode: async`, die Antwort enthält `poll`). Scope `batch`.\nDie Antwort ist immer `200` mit Body; bei asynchronen Läufen fragen Sie `poll` ab. Obergrenze 100 000 Zeilen je Aufruf.\nFür Dateien siehe `/v1/upload`. Mit dem Header `Idempotency-Key` ist der Aufruf wiederholbar, ohne einen zweiten Lauf zu starten.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "items": [
                  {
                    "name": "Bernard Madoff",
                    "birth_year": 1938,
                    "country": "US"
                  },
                  {
                    "name": "Erika Mustermann",
                    "country": "DE"
                  }
                ],
                "threshold": 0.85,
                "limit": 10,
                "async_mode": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job angelegt (async) oder fertig (sync).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchCreated"
                },
                "example": {
                  "ok": true,
                  "job_id": "6c0f2a6e-62d8-4a43-9b2e-7d2d3f6a9b10",
                  "status": "queued",
                  "mode": "async",
                  "total": 2,
                  "done": 0,
                  "hit_count": 0,
                  "poll": "/v1/batch/6c0f2a6e-62d8-4a43-9b2e-7d2d3f6a9b10",
                  "decision": "signal_only",
                  "true_match": false,
                  "tenant_id": "demo"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "Mehr als 5000 Zeilen (`detail: batch max 5000 items`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/batch\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"items\": [{\"name\": \"Bernard Madoff\", \"birth_year\": 1938, \"country\": \"US\"}, {\"name\": \"Erika Mustermann\", \"country\": \"DE\"}], \"threshold\": 0.85, \"limit\": 10, \"async_mode\": true}'"
          }
        ]
      }
    },
    "/v1/batch/{job_id}": {
      "get": {
        "operationId": "getBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Job-Status samt aller Items",
        "description": "Liefert Status und **alle Items inline** (`items[]` mit Kurzsummary je Person). Für grosse Läufe ungeeignet:\nnutzen Sie `/summary`, `/positives` und `/breakdown`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Job mit Items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchJob"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/upload": {
      "post": {
        "operationId": "uploadBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Datei oder Text hochladen und asynchron prüfen",
        "description": "CSV oder XLSX (Spalten werden über Header-Aliase erkannt) oder `paste` (Text, eine Person je Zeile).\nGrenzen: 50 MiB, 100 000 Zeilen, strikter Upload-Zähler je Schlüssel. Die Antwort ist immer asynchron (`mode: async`).\nLegacy-`.xls` wird mit 415 abgelehnt.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "CSV oder XLSX"
                  },
                  "paste": {
                    "type": "string",
                    "description": "Alternativ: Text, eine Person je Zeile"
                  },
                  "threshold": {
                    "type": "number",
                    "minimum": 0.5,
                    "maximum": 1.0,
                    "default": 0.85
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 10
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job angelegt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadAccepted"
                },
                "example": {
                  "ok": true,
                  "job_id": "6c0f2a6e-62d8-4a43-9b2e-7d2d3f6a9b10",
                  "total": 1200,
                  "mode": "async",
                  "threshold": 0.85,
                  "limit": 10,
                  "poll": "/v1/batch/6c0f2a6e-62d8-4a43-9b2e-7d2d3f6a9b10",
                  "note": "Poll the poll URL for progress.",
                  "tenant_id": "demo"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "Datei zu gross, zu viele Zeilen oder Job über 5000 Zeilen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "415": {
            "description": "Format nicht unterstützt (z. B. `.xls`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/upload\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -F \"file=@bestand.csv\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/summary": {
      "get": {
        "operationId": "getBatchSummary",
        "tags": [
          "Batches"
        ],
        "summary": "Triage-Zahlen und Herkunft des Jobs",
        "description": "Personen mit Treffer (`positives`), nur Hinweise (`hints_only`), Zustände und Dispositionen, Review-Richtlinie\n(`review_policy`, enthält `four_eyes`), Herkunft (`result_version`, `code_commit`, `matcher_config_hash`,\n`checked_against`). Scope `batch`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Zusammenfassung.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchSummary"
                },
                "example": {
                  "job_id": "6c0f2a6e-62d8-4a43-9b2e-7d2d3f6a9b10",
                  "status": "completed",
                  "total": 1200,
                  "done": 1200,
                  "positives": 14,
                  "hints_only": 9,
                  "hit_count": 21,
                  "mode": "async",
                  "created_at": "2026-10-07T07:50:00+00:00",
                  "updated_at": "2026-10-07T07:58:41+00:00",
                  "dispositions": {
                    "open": 12,
                    "false_positive": 2,
                    "confirmed": 0
                  },
                  "states": {
                    "open": 12,
                    "proposed_false_positive": 0,
                    "proposed_confirmed": 0,
                    "escalated": 0,
                    "false_positive": 2,
                    "confirmed": 0
                  },
                  "awaiting_approval": 0,
                  "review_policy": {
                    "four_eyes": true,
                    "min_reason_len": 10,
                    "identity": "claimed_name"
                  },
                  "result_version": 2,
                  "code_commit": "3fa91c2",
                  "matcher_config_hash": "7d2e51b0",
                  "checked_against": {
                    "sources": 93,
                    "mandatory": [],
                    "oldest_list_date": "2026-10-05T03:00:00Z"
                  },
                  "decision": "signal_only",
                  "true_match": false
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/summary\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/positives": {
      "get": {
        "operationId": "listBatchPositives",
        "tags": [
          "Batches"
        ],
        "summary": "Personen mit Treffer (Tabellenzeilen, Offset-Paginierung)",
        "description": "Eine Zeile je Person mit `top_band`, `top_hit` (`Hit`), `hit_count`, `record_count`, Kategorien und Bearbeitungsstand.\nStandard: nur Alarme. `include=hints` ergänzt Hinweis-Zeilen (nur ausgeblendete Treffer), `include=hints_only` liefert nur diese.\nPaginierung über `offset` und `limit`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Namens-Substring",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "disposition",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "false_positive",
                "confirmed"
              ]
            }
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/TriageState"
            }
          },
          {
            "name": "min_score",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "score",
                "name"
              ],
              "default": "score"
            }
          },
          {
            "name": "include",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "hints",
                "hints_only"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Seite der Personenzeilen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PositivesPage"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/positives\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/positives/{seq}": {
      "get": {
        "operationId": "getBatchPositive",
        "tags": [
          "Batches"
        ],
        "summary": "Eine Person mit allen Treffern und Hinweisen",
        "description": "Dieselbe Zeile wie in der Liste plus `hits[]` (alle, zusammengeführt, sortiert Band, dann Score) und `suppressed_hits[]` (höchstens 20).",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/Seq"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Person im Detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PositiveRow"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/positives/<seq>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/positives/{seq}/history": {
      "get": {
        "operationId": "getBatchPositiveHistory",
        "tags": [
          "Batches"
        ],
        "summary": "Verlauf einer Person (append-only)",
        "description": "Wer, wann, von nach, Begründung, Version. Älteste zuerst.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/Seq"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Ereignisse.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PositiveHistory"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/positives/<seq>/history\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/dispositions": {
      "get": {
        "operationId": "getBatchDispositions",
        "tags": [
          "Batches"
        ],
        "summary": "Stand der Entscheide im Job",
        "description": "Zähler je Disposition und Zustand, `awaiting_approval`, `review_policy`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Zähler.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchDispositions"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/dispositions\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/export": {
      "get": {
        "operationId": "exportBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Export (CSV oder JSON Lines)",
        "description": "Eine Zeile je Person und Treffer mit lesbaren Spalten (Anfrage, Listenname, Stufe, Stufen-Grund, Quelle, Kategorie,\nListenstand, URL, verstorben, Anzahl Einträge); technische IDs am Ende. CSV mit UTF-8-BOM; Zellen, die mit `= + - @`\nbeginnen, werden mit `'` präfixt (Schutz vor Formel-Injektion in Tabellenkalkulationen).",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "jsonl"
              ],
              "default": "csv"
            }
          },
          {
            "name": "disposition",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "false_positive",
                "confirmed"
              ]
            }
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/TriageState"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Datei-Download.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/export\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/breakdown": {
      "get": {
        "operationId": "getBatchBreakdown",
        "tags": [
          "Batches"
        ],
        "summary": "Aufschlüsselung (Trichter, Stärke, Kategorie, Quellen, Überschneidungen)",
        "description": "Zählt Personen nach Treffer-Vertrag §2 (zusammengeführt). Gleiche Operation unter dem Alias `/v1/jobs/{job_id}/breakdown`.\nOptionaler Block `adverse_media` (Kategorie `adverse_media_signal`, nur identitätsgebundene Verknüpfungen).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Aufschlüsselung.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Breakdown"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/breakdown\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/persons": {
      "get": {
        "operationId": "listBatchPersons",
        "tags": [
          "Batches"
        ],
        "summary": "Drill-down auf Personen-IDs (Filter nach Stärke, Quelle, Kategorie, Listenanzahl)",
        "description": "Alias `/v1/jobs/{job_id}/persons`. Offset-Paginierung; `person_id` ist die `seq` aus `/positives`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "name": "strength",
            "in": "query",
            "description": "high,review,soft,below (Komma-Liste)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "description": "source_id (Komma-Liste)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "description": "Kategorie (Komma-Liste)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lists_min",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "lists_max",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Seite von Personen.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "`job_id`, `total`, `count`, `offset`, `next_offset`, `person_ids[]`, `persons[]` (`person_id`, `seq`, `query_name`, `strongest_band`, `source_count`, `sources`, `categories`), `filters`."
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/persons\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/delta": {
      "get": {
        "operationId": "getBatchDelta",
        "tags": [
          "Batches"
        ],
        "summary": "Delta zweier Läufe (neu, weg, eskaliert, de-eskaliert, Quellenwechsel)",
        "description": "Vergleicht diesen Lauf mit `against` (Standard: der per `rerun` verkettete Vorgänger). `warnings` nennt unfertige\noder alte Läufe.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "name": "against",
            "in": "query",
            "description": "Vorgänger-Job (UUID)",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Delta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchDelta"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/batch/<job_id>/delta\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/batch/{job_id}/rerun": {
      "post": {
        "operationId": "rerunBatch",
        "tags": [
          "Batches"
        ],
        "summary": "Dieselben Zeilen neu prüfen (verketteter Job)",
        "description": "Legt einen neuen asynchronen Job mit denselben Items an (`previous_job_id` gesetzt). Gleiche Grenzen wie `POST /v1/batch`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Neuer Job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchCreated"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "description": "Job grösser als 5000 Zeilen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/batch/<job_id>/rerun\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/sources/catalog": {
      "get": {
        "operationId": "getSourcesCatalog",
        "tags": [
          "Sources"
        ],
        "summary": "Quellen-Katalog (schlanke Sicht)",
        "description": "Id, Label, Land, Kategorie, Art, Listenstand (`list_date` + Basis), Entitätenzahl, URL. Scope `batch`.",
        "responses": {
          "200": {
            "description": "Katalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourcesCatalog"
                },
                "example": {
                  "count": 1,
                  "sources": [
                    {
                      "id": "ch_seco",
                      "label": "SECO-Sanktionsliste (Schweiz)",
                      "country": "CH",
                      "category": "sanctions",
                      "kind": "list",
                      "list_date": "2026-10-06T18:00:00Z",
                      "list_date_basis": "fetched",
                      "entity_count": 5300,
                      "url": "https://www.seco.admin.ch/"
                    }
                  ],
                  "decision": "signal_only",
                  "true_match": false
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/sources/catalog\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/evidence/{request_id}": {
      "get": {
        "operationId": "getEvidence",
        "tags": [
          "Evidence"
        ],
        "summary": "Signierter Nachweis zu einer Einzelprüfung",
        "description": "Audit-Spur mit `list_versions` (inkl. `content_hash`, `fetched_at`), Kandidaten mit SHA-256, `input_sha256`,\n`code_commit`, `config_hash` und Ed25519-Signatur über kanonisches JSON. Scope `cases`.\nDer öffentliche Schlüssel zur Prüfung steht im Nachweis (`signature.public_key`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "markdown"
              ],
              "default": "json"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Nachweis.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Evidence"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/evidence/<request_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/evidence/{request_id}/report": {
      "get": {
        "operationId": "getEvidenceReport",
        "tags": [
          "Evidence"
        ],
        "summary": "Beispielantwort (gekürzt)",
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "markdown"
              ],
              "default": "json"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Bericht.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/evidence/<request_id>/report\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/evidence/{request_id}/bundle": {
      "get": {
        "operationId": "getEvidenceBundle",
        "tags": [
          "Evidence"
        ],
        "summary": "Audit-Bundle mit Reproduktionsschritten",
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "markdown"
              ],
              "default": "json"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Bundle.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/evidence/<request_id>/bundle\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Beispielantwort (gekürzt)",
        "description": "Mandant, Plan, Scopes, Kontingente und Präfix des verwendeten Schlüssels.",
        "responses": {
          "200": {
            "description": "Identität des Schlüssels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/me\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "tags": [
          "Account"
        ],
        "summary": "Nutzung und Kontingent",
        "responses": {
          "200": {
            "description": "Zähler.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/usage\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/monitors": {
      "get": {
        "operationId": "listMonitors",
        "tags": [
          "Monitoring"
        ],
        "summary": "Monitore listen",
        "responses": {
          "200": {
            "description": "Monitore.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/monitors\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      },
      "post": {
        "operationId": "createMonitor",
        "tags": [
          "Monitoring"
        ],
        "summary": "Einzelperson dauerhaft überwachen (Monitor)",
        "description": "Scope `monitor`. Cadence `on_list_update` (Standard), `hourly`, `daily`, `weekly`. Ein Monitor ist eine Einzelabfrage;\ngemeldet wird nur, was sich gegenüber der letzten Meldung geändert hat (neu, verändert, entfallen). Treffer des Monitors sind eine Kurzform.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MonitorCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Monitor angelegt.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "`monitor`, `disclaimer`, `decision`."
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/monitors\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ]
      }
    },
    "/v1/monitors/{monitor_id}": {
      "get": {
        "operationId": "getMonitor",
        "tags": [
          "Monitoring"
        ],
        "summary": "Monitor lesen",
        "responses": {
          "200": {
            "description": "Monitor.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/monitors/<monitor_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      },
      "patch": {
        "operationId": "patchMonitor",
        "tags": [
          "Monitoring"
        ],
        "summary": "Monitor ändern",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MonitorCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Geändert.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X PATCH \"$KOIVU_URL/v1/monitors/<monitor_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ]
      },
      "delete": {
        "operationId": "deleteMonitor",
        "tags": [
          "Monitoring"
        ],
        "summary": "Monitor löschen",
        "responses": {
          "200": {
            "description": "Gelöscht.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X DELETE \"$KOIVU_URL/v1/monitors/<monitor_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      },
      "parameters": [
        {
          "name": "monitor_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ]
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhooksLegacy",
        "tags": [
          "Monitoring"
        ],
        "summary": "Webhooks listen (Altformat)",
        "responses": {
          "200": {
            "description": "Webhooks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/webhooks\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      },
      "post": {
        "operationId": "createWebhookLegacy",
        "tags": [
          "Monitoring"
        ],
        "summary": "Webhook registrieren (Altformat)",
        "description": "Header `X-Screening-Signature`, `X-Screening-Timestamp`, `X-Screening-Delivery-Id`; Signatur HMAC-SHA256 (hex) über\n`<timestamp>.<delivery-id>.<body>`; Events `monitor.hit`, `monitor.list_removed`, `list.updated`, `webhook.test`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registriert.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/webhooks\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ]
      }
    },
    "/v1/webhooks/{webhook_id}/test": {
      "post": {
        "operationId": "testWebhookLegacy",
        "tags": [
          "Monitoring"
        ],
        "summary": "Signiertes Test-Event senden (Altformat)",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "secret"
                ],
                "properties": {
                  "secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gesendet.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "400": {
            "description": "Test fehlgeschlagen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/webhooks/<webhook_id>/test\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ]
      }
    },
    "/v1/webhooks/{webhook_id}/rotate": {
      "post": {
        "operationId": "rotateWebhookLegacy",
        "tags": [
          "Monitoring"
        ],
        "summary": "Webhook-Secret rotieren (Altformat)",
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Neues Secret.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/webhooks/<webhook_id>/rotate\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    },
    "/v1/cases": {
      "get": {
        "operationId": "listCases",
        "tags": [
          "Monitoring"
        ],
        "summary": "Fälle listen",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Fälle.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/cases\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      },
      "post": {
        "operationId": "createCase",
        "tags": [
          "Monitoring"
        ],
        "summary": "Fall aus einer Prüfung anlegen",
        "description": "Scope `cases`. Mindestens eines von `request_id`, `match_ref`, `query_name`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request_id": {
                    "type": "string"
                  },
                  "match_ref": {
                    "type": "string"
                  },
                  "query_name": {
                    "type": "string"
                  },
                  "monitor_id": {
                    "type": "string"
                  },
                  "assignee": {
                    "type": "string"
                  },
                  "note": {
                    "type": "string"
                  },
                  "actor_person": {
                    "type": "string"
                  },
                  "hit_summary": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "list_versions": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "screening_roles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "ubo_declaration": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fall.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "`case`, `decision`, `true_match`, `disclaimer`."
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X POST \"$KOIVU_URL/v1/cases\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ]
      }
    },
    "/v1/cases/{case_id}": {
      "get": {
        "operationId": "getCase",
        "tags": [
          "Monitoring"
        ],
        "summary": "Fall lesen",
        "responses": {
          "200": {
            "description": "Fall.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/cases/<case_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      },
      "patch": {
        "operationId": "patchCase",
        "tags": [
          "Monitoring"
        ],
        "summary": "Fall bearbeiten (Status, Zuständigkeit, Notiz)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Geändert.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X PATCH \"$KOIVU_URL/v1/cases/<case_id>\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ]
      },
      "parameters": [
        {
          "name": "case_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ]
    },
    "/v1/cases/{case_id}/events": {
      "get": {
        "operationId": "listCaseEvents",
        "tags": [
          "Monitoring"
        ],
        "summary": "Verlauf eines Falls",
        "parameters": [
          {
            "name": "case_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/KoivuVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Ereignisse.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            },
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -s -X GET \"$KOIVU_URL/v1/cases/<case_id>/events\" \\\n  -H \"Authorization: Bearer $KOIVU_KEY\""
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_live_… / sk_test_…",
        "description": "Ihr Schlüssel als `Authorization: Bearer sk_live_…`. `sk_test_…` zum Ausprobieren (engere Limits)."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Eindeutiger Wert (1 bis 255 sichtbare ASCII-Zeichen). Die erste erfolgreiche Antwort wird 24 Stunden gespeichert und bei Wiederholung ausgeliefert (Header `Idempotent-Replayed: true`). Anderer Inhalt mit demselben Key: 422 `idempotency_key_reused`; läuft die erste Anfrage noch: 409 `idempotency_in_progress`.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        }
      },
      "JobId": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "description": "Batch-Job-ID (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "KoivuVersion": {
        "name": "Koivu-Version",
        "in": "header",
        "required": false,
        "description": "Datumsbasierte API-Version, überschreibt den Mandanten-Pin je Aufruf. Unbekannte Felder müssen Clients ignorieren.",
        "schema": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "examples": [
            "2026-10-15"
          ]
        }
      },
      "RequestId": {
        "name": "request_id",
        "in": "path",
        "required": true,
        "description": "`request_id` aus der Antwort von `/v1/match` (UUID).",
        "schema": {
          "type": "string"
        }
      },
      "Seq": {
        "name": "seq",
        "in": "path",
        "required": true,
        "description": "Zeilennummer der Person im Job (`batch_items.seq`); gilt nur je Job.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Korrelations-ID der Anfrage, auch bei Fehlern. Bitte bei Rückfragen nennen.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Sekunden bis zum nächsten sinnvollen Versuch.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitLimit": {
        "description": "Anfragen pro Minute für diesen Schlüssel.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitRemaining": {
        "description": "Verbleibende Anfragen im laufenden Minutenfenster.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitReset": {
        "description": "Unix-Zeit, zu der das Minutenfenster endet.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Schlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (401). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Der Schlüssel hat den Scope nicht oder die Quell-IP ist nicht erlaubt (403). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ressource nicht gefunden oder gehört einem anderen Mandanten (404). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      },
      "Invalid": {
        "description": "Eingabe ungültig (422/400). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      },
      "TooMany": {
        "description": "Minutenlimit oder Tageskontingent erreicht (429). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      },
      "Unavailable": {
        "description": "Dienst, Datenbank oder Pflichtliste vorübergehend nicht verfügbar (503). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      },
      "Conflict": {
        "description": "Konflikt: Idempotency-Key läuft noch (409) oder wurde mit anderen Parametern benutzt (422). Mit `Koivu-Version: 2026-10-15` als `application/problem+json`, sonst als `{\"detail\": …}`.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LegacyError"
            }
          }
        }
      }
    },
    "schemas": {
      "AlertPolicy": {
        "type": "string",
        "enum": [
          "standard",
          "strict",
          "off"
        ],
        "default": "standard",
        "description": "Alarm-Regeln je Quellenart. `standard`: Sanktionen immer; Tier 2 (PEP national/ausländisch, Enforcement) ab Namens-Score 0,95\noder bestätigter Identität; Tier 3 (PEP subnational, RCA) nur bei Score ≥ 0,95 **und** bestätigter Identität. `off` = altes Verhalten.\n"
      },
      "BatchCreated": {
        "type": "object",
        "description": "Antwort von `POST /v1/batch` und `/rerun`. `sync`: mit `responses` (Map wie bei `/v1/match`); `async`: mit `poll`.",
        "required": [
          "ok",
          "job_id",
          "status",
          "mode",
          "total",
          "decision",
          "true_match"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "completed",
              "partial",
              "failed"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "sync",
              "async"
            ]
          },
          "total": {
            "type": "integer"
          },
          "done": {
            "type": "integer"
          },
          "hit_count": {
            "type": "integer"
          },
          "poll": {
            "type": "string",
            "description": "Pfad der Statusabfrage (`/v1/batch/{job_id}`)."
          },
          "note": {
            "type": "string"
          },
          "responses": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/QueryResult"
            }
          },
          "child_request_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "job_audit_request_id": {
            "type": "string"
          },
          "latency_ms": {
            "type": "integer"
          },
          "previous_job_id": {
            "type": "string",
            "format": "uuid"
          },
          "disclaimer": {
            "type": "string"
          },
          "matcher": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string"
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "BatchDelta": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "previous_job_id": {
            "type": "string",
            "format": "uuid"
          },
          "current": {
            "type": "object",
            "additionalProperties": true,
            "description": "`job_id`, `status`, `total`, `created_at`, `finished_at`."
          },
          "previous": {
            "type": "object",
            "additionalProperties": true
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string",
              "examples": [
                "current_job_not_finished",
                "previous_job_not_finished",
                "current_job_legacy_format"
              ]
            }
          },
          "summary": {
            "type": "object",
            "description": "Zähler je Kategorie plus `unchanged`, `current_persons`, `previous_persons`.",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "new": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "gone": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "escalated": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "deescalated": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "changed_sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "BatchDispositions": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "total_positives": {
            "type": "integer"
          },
          "dispositions": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "states": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "awaiting_approval": {
            "type": "integer"
          },
          "review_policy": {
            "type": "object",
            "additionalProperties": true
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "BatchJob": {
        "type": "object",
        "description": "Lauf mit **allen Items inline** (für grosse Läufe `/summary`, `/positives` und `/breakdown` nutzen).",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "sync",
              "async"
            ]
          },
          "total": {
            "type": "integer"
          },
          "done": {
            "type": "integer"
          },
          "hit_count": {
            "type": "integer"
          },
          "threshold": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "result_version": {
            "type": "integer",
            "description": "1 = ältere Kurzform, 2 = Treffer-Vertrag v2."
          },
          "code_commit": {
            "type": [
              "string",
              "null"
            ]
          },
          "matcher_config_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "checked_against": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CheckedAgainst"
              },
              {
                "type": "null"
              }
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "seq": {
                  "type": "integer"
                },
                "request_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "hit_count": {
                  "type": "integer"
                },
                "status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "summary": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "poll": {
            "type": "string"
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "maxItems": 5000,
            "description": "Zeilen wie `MatchRequest` (Kurzform): `name`, `birth_year`, `country`, `schema`, `id_number`.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "threshold": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 1.0
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "datasets": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "async_mode": {
            "type": "boolean",
            "description": "Auch bis 50 Zeilen asynchron erzwingen."
          }
        }
      },
      "BatchSummary": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "done": {
            "type": "integer"
          },
          "positives": {
            "type": "integer",
            "description": "Personen mit mindestens einem Alarm."
          },
          "hints_only": {
            "type": "integer",
            "description": "Personen nur mit Hinweisen (ohne Alarm)."
          },
          "hit_count": {
            "type": "integer"
          },
          "mode": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "dispositions": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Zähler `open`, `false_positive`, `confirmed`."
          },
          "states": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "awaiting_approval": {
            "type": "integer"
          },
          "review_policy": {
            "type": "object",
            "description": "Maschinenlesbare Richtlinie. Enthält `four_eyes`, `states`, `actions`, `reason_codes`, `min_reason_len`, `transitions`, `identity` (der Name ist angegeben, nicht per Login geprüft).",
            "additionalProperties": true
          },
          "result_version": {
            "type": "integer"
          },
          "code_commit": {
            "type": [
              "string",
              "null"
            ]
          },
          "matcher_config_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "checked_against": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CheckedAgainst"
              },
              {
                "type": "null"
              }
            ]
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "Breakdown": {
        "type": "object",
        "description": "Aufschlüsselung eines Jobs (Personen nach Vertrag §2, nicht Rohdatensätze).",
        "required": [
          "job_id",
          "decision",
          "true_match",
          "funnel",
          "by_strength",
          "by_category",
          "sources"
        ],
        "properties": {
          "job_id": {
            "type": "string"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "decision": {
            "type": "string"
          },
          "true_match": {
            "type": "boolean"
          },
          "result_version": {
            "type": "integer",
            "description": "1 = ältere Kurzform, 2 = Vertrag v2."
          },
          "code_commit": {
            "type": [
              "string",
              "null"
            ]
          },
          "matcher_config_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "checked_against": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CheckedAgainst"
              },
              {
                "type": "null"
              }
            ]
          },
          "funnel": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Eingereichte Namen."
              },
              "done": {
                "type": "integer"
              },
              "with_hit": {
                "type": "integer"
              },
              "without_hit": {
                "type": "integer"
              },
              "hints_only": {
                "type": "integer"
              },
              "hits": {
                "type": "integer"
              },
              "hits_reported": {
                "type": "integer"
              },
              "truncated_persons": {
                "type": "integer"
              },
              "record_count": {
                "type": "integer"
              }
            }
          },
          "by_strength": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "band": {
                  "type": "string",
                  "enum": [
                    "high",
                    "review",
                    "soft",
                    "below"
                  ]
                },
                "label_de": {
                  "type": "string"
                },
                "persons": {
                  "type": "integer"
                },
                "hits": {
                  "type": "integer"
                }
              }
            }
          },
          "by_category": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "category": {
                  "type": "string"
                },
                "persons": {
                  "type": "integer"
                },
                "hits": {
                  "type": "integer"
                }
              }
            }
          },
          "by_pep_category": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pep_category": {
                  "type": "string"
                },
                "persons": {
                  "type": "integer"
                },
                "hits": {
                  "type": "integer"
                }
              }
            }
          },
          "persons_by_source_count": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "bucket": {
                  "type": "string",
                  "description": "0, 1, 2, 3-5, 6+"
                },
                "min": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "max": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "persons": {
                  "type": "integer"
                }
              }
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "source_id": {
                  "type": "string"
                },
                "category": {
                  "type": "string"
                },
                "persons": {
                  "type": "integer"
                },
                "hits": {
                  "type": "integer"
                }
              }
            }
          },
          "overlaps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "categories": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 2,
                  "maxItems": 2
                },
                "persons": {
                  "type": "integer"
                }
              }
            }
          },
          "adverse_media": {
            "anyOf": [
              {
                "type": "object",
                "description": "Additiver Block; nur identitätsgebundene Verknüpfungen.",
                "properties": {
                  "category": {
                    "type": "string",
                    "const": "adverse_media_signal"
                  },
                  "available": {
                    "type": "boolean"
                  },
                  "persons": {
                    "type": "integer"
                  },
                  "links": {
                    "type": "integer"
                  },
                  "clusters": {
                    "type": "integer"
                  },
                  "domains": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "domain": {
                          "type": "string"
                        },
                        "links": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "signal_only": {
                    "type": "boolean"
                  },
                  "true_match": {
                    "type": "boolean"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "CheckedAgainst": {
        "type": "object",
        "description": "Wogegen geprüft wurde (Treffer-Vertrag §5).",
        "properties": {
          "sources": {
            "type": "integer",
            "description": "Anzahl Quellen im Index zum Zeitpunkt der Prüfung."
          },
          "mandatory": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "list_date": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "list_date_basis": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "published",
                    "fetched",
                    null
                  ]
                }
              }
            }
          },
          "oldest_list_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ConfidenceBand": {
        "type": "string",
        "enum": [
          "high",
          "review",
          "soft"
        ],
        "description": "Stufe nach Treffer-Vertrag v2 §3. `high` ≥ 0,98 Namens-Score, `review` 0,90 bis 0,97, `soft` darunter. Nie ein Entscheid."
      },
      "Evidence": {
        "type": "object",
        "description": "Selbsttragender, signierter Nachweis (Ed25519 über kanonisches JSON). Enthält Eingabe-Hash, Kandidaten mit SHA-256,\n`list_versions` (inkl. `content_hash`, `fetched_at`), `code_commit`, `config_hash`. Der öffentliche Schlüssel steht im Nachweis.",
        "properties": {
          "schema_version": {
            "type": "integer"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "signed_scope": {
            "type": "object",
            "description": "Signierter Umfang: `kind`, `request_id`, `tenant_id`, `ts_utc`, `code_commit`, `config_hash`, `matcher_config_version`, `matcher`, `threshold_requested`, `effective_thresholds`, `list_versions`, Kandidaten, `input_sha256`.",
            "additionalProperties": true
          },
          "hash_algorithm": {
            "type": "string",
            "examples": [
              "sha256"
            ]
          },
          "canonicalization": {
            "type": "string",
            "description": "Kanonisierung des signierten JSON: `json.dumps(sort_keys=True, separators=(',',':'))`."
          },
          "excluded_from_hash": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "report_hash": {
            "type": "string",
            "description": "`sha256(canonical_json(signed_scope))`."
          },
          "signature": {
            "type": "object",
            "properties": {
              "algorithm": {
                "type": "string",
                "examples": [
                  "ed25519"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "signed",
                  "unsigned"
                ],
                "description": "`signed`, oder `unsigned`, wenn der Dienst keinen Signierschlüssel hat. Ein unsignierter Nachweis ist kein Beleg."
              },
              "public_key": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Hex. Öffentlicher Schlüssel zur Prüfung der Signatur."
              },
              "signature": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Base64."
              },
              "payload_sha256": {
                "type": "string"
              }
            }
          },
          "verify_hint": {
            "type": "string"
          }
        }
      },
      "Hit": {
        "type": "object",
        "description": "**Ein Treffer, überall gleich** (Treffer-Vertrag v2 §1): `/v1/match`, Batch-Positives, Export, Alerts und Webhooks tragen dieselbe Form.\nDubletten derselben gelisteten Person sind zu **einem** `Hit` zusammengeführt (§2); die Rohdatensätze stehen in `records`.\nEin `Hit` ist ein Signal, kein Befund: `confidence_band` und `explain` erklären, warum er erscheint.\n",
        "required": [
          "entity_id",
          "caption",
          "score",
          "confidence_band",
          "explain",
          "source",
          "records"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Heute identisch zu `entity_id` (Altfeld, bleibt)."
          },
          "entity_id": {
            "type": "string",
            "examples": [
              "ch-seco-12345"
            ]
          },
          "match": {
            "type": "boolean",
            "description": "Altfeld des Matchers (Schwelle erreicht). Kein Entscheid."
          },
          "true_match": {
            "type": "boolean",
            "const": false,
            "description": "Immer `false`: Koivu stellt nie einen Treffer als bestätigt fest."
          },
          "score_adjusted": {
            "type": "number",
            "description": "Altfeld, Score nach Boni."
          },
          "profile_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Zusammenführungs-Schlüssel (Treffer-Vertrag §2)."
          },
          "also_on": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Weitere Quellen derselben Person (Altfeld)."
          },
          "risk_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Risikomodell (Zusatz, additiv); `risk_band`, `risk_reasons`, `risk_contributions`, `risk_model_version` folgen demselben Muster."
          },
          "risk_band": {
            "type": [
              "string",
              "null"
            ]
          },
          "caption": {
            "type": "string",
            "description": "Anzeigename."
          },
          "caption_original": {
            "type": [
              "string",
              "null"
            ],
            "description": "Originalschreibweise, wenn abweichend."
          },
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1.5,
            "description": "Ranking-Score; darf Boni enthalten (Geburtsjahr +0,05, ID +0,10). Die Stufe hängt nicht an den Boni."
          },
          "confidence_band": {
            "$ref": "#/components/schemas/ConfidenceBand"
          },
          "explain": {
            "$ref": "#/components/schemas/HitExplain"
          },
          "source": {
            "$ref": "#/components/schemas/HitSource"
          },
          "records": {
            "type": "array",
            "description": "Alle Listeneinträge dieser gelisteten Person.",
            "items": {
              "type": "object",
              "properties": {
                "source_id": {
                  "type": "string"
                },
                "entity_id": {
                  "type": "string"
                },
                "list_version": {
                  "type": "string"
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "deceased": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`null` = unbekannt (nie `false`, wenn nicht berechnet)."
          },
          "death_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "deceased_basis": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "wikidata_p570"
            ]
          },
          "pep_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "pep_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_rca": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "features": {
            "type": "object",
            "properties": {
              "birth_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "birth_year": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "countries": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "nationality": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "aliases": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "HitExplain": {
        "type": "object",
        "description": "Gründe mit Teilwerten („Name exakt, Geburtsjahr passt, Land passt“).",
        "properties": {
          "name": {
            "type": "object",
            "properties": {
              "query": {
                "type": "string"
              },
              "listed": {
                "type": "string",
                "description": "Der Name, der tatsächlich gepasst hat (nie das Skelett)."
              },
              "matched_via": {
                "type": "string",
                "enum": [
                  "primary",
                  "alias"
                ]
              },
              "kind": {
                "type": "string",
                "enum": [
                  "exact",
                  "near",
                  "reordered",
                  "partial",
                  "initials",
                  "transliteration",
                  "phonetic",
                  "split"
                ]
              },
              "score": {
                "type": "number",
                "description": "Reiner Namens-Score ohne Boni."
              },
              "tokens": {
                "type": "object",
                "properties": {
                  "common": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "query_only": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "listed_only": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "birth": {
            "type": "object",
            "properties": {
              "query": {
                "type": [
                  "integer",
                  "string",
                  "null"
                ]
              },
              "listed": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "listed_years": {
                "type": "array",
                "items": {
                  "type": "integer"
                },
                "description": "Alle Geburtsjahre der Listung."
              },
              "result": {
                "type": "string",
                "enum": [
                  "confirmed",
                  "not_comparable",
                  "conflict"
                ]
              }
            }
          },
          "country": {
            "type": "object",
            "properties": {
              "query": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "listed": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "result": {
                "type": "string",
                "enum": [
                  "confirmed",
                  "not_comparable",
                  "conflict"
                ]
              }
            }
          },
          "id_number": {
            "type": "object",
            "properties": {
              "result": {
                "type": "string",
                "enum": [
                  "confirmed",
                  "not_comparable",
                  "conflict"
                ]
              }
            }
          },
          "identity": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_checkable",
              "conflict"
            ]
          },
          "band_reason": {
            "type": "string",
            "description": "Code nach Treffer-Vertrag §3, z. B. `name_exact_identity_confirmed`, `name_near_unconfirmed`, `phonetic_only`, `country_conflict`, `deceased`, `unconfirmed_similar`, `low_risk_unconfirmed`."
          },
          "policy": {
            "type": "object",
            "properties": {
              "class": {
                "type": "string",
                "description": "Quellenart, z. B. `sanctions`."
              },
              "mode": {
                "$ref": "#/components/schemas/AlertPolicy"
              },
              "action": {
                "type": "string",
                "description": "`keep` oder `suppress` (bei ausgeblendeten Hinweisen)."
              }
            }
          }
        }
      },
      "HitSource": {
        "type": "object",
        "required": [
          "id",
          "category"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "ch_seco"
            ]
          },
          "label": {
            "type": "string"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string",
            "description": "Dieselben Kategorien wie `Breakdown.by_category` (`sanctions`, `pep_core`, `enforcement`, `adverse_media_signal`, `ubo`, `kyb`, `unknown`)."
          },
          "list_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "list_date_basis": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "published",
              "fetched",
              null
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Eintrag, sonst Liste; null wenn unbekannt."
          },
          "url_kind": {
            "type": "string",
            "enum": [
              "entry",
              "list"
            ],
            "description": "Ob `url` auf den Eintrag oder die Liste zeigt."
          }
        }
      },
      "LegacyError": {
        "type": "object",
        "description": "Fehlerformat ohne Opt-in (ohne Header `Koivu-Version: 2026-10-15`). Varianten: `{detail}` und\n`{detail, error, request_id}` (503/500). Mit dem Opt-in antworten alle Fehler als `Problem`.",
        "properties": {
          "detail": {
            "description": "Text oder Objekt `{code, message}`.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            ]
          },
          "error": {
            "type": "string",
            "description": "z. B. `database_unavailable`, `internal_error`."
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "MatchRequest": {
        "type": "object",
        "description": "Entweder `queries` (OpenSanctions-nahe Form, höchstens 50) oder die Kurzform (`name` …). Grenzen: `limit` 1 bis 50, `threshold` 0,5 bis 1,0.",
        "properties": {
          "queries": {
            "type": "object",
            "maxProperties": 50,
            "description": "Map Query-ID auf Query.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "Person",
                    "Organization"
                  ],
                  "description": "Schema-Name (Person oder Organisation)."
                },
                "properties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "description": "z. B. `name`, `birthDate`, `country`, `idNumber`."
                },
                "id": {
                  "type": "string"
                }
              }
            }
          },
          "name": {
            "type": "string"
          },
          "schema": {
            "type": "string"
          },
          "datasets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Quellen-IDs; leer = alle aktiven."
          },
          "threshold": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 1.0
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "birth_year": {
            "type": "integer"
          },
          "country": {
            "type": "string",
            "description": "ISO-3166-Alpha-2"
          },
          "id_number": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "description": "Rolle nach Art. 13 Abs. 5 GwV-FINMA."
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "include_context": {
            "type": "boolean",
            "default": false,
            "description": "Adverse-Media-Kontextsignale ohne Personen-ID-Link separat als `context_signals[]` (nie Alert)."
          },
          "include_deceased": {
            "type": "boolean",
            "default": false
          },
          "alert_policy": {
            "$ref": "#/components/schemas/AlertPolicy"
          },
          "adverse_media_elevates_risk": {
            "type": "boolean",
            "default": false
          },
          "ubo_declaration": {
            "type": "object",
            "description": "Erklärter wirtschaftlich Berechtigter (nie vom System ermittelt).",
            "required": [
              "person_name",
              "declared_at",
              "declarant"
            ],
            "properties": {
              "person_name": {
                "type": "string"
              },
              "declared_at": {
                "type": "string",
                "format": "date"
              },
              "declarant": {
                "type": "string"
              }
            }
          },
          "customer_type": {
            "type": "string"
          },
          "art7a_exemption": {
            "type": "boolean",
            "default": false
          },
          "art7a_reason": {
            "type": "string"
          },
          "art7a_amount": {
            "type": "number",
            "minimum": 0
          },
          "art7a_currency": {
            "type": "string"
          }
        }
      },
      "MatchResponse": {
        "type": "object",
        "required": [
          "responses",
          "request_id",
          "decision",
          "true_match"
        ],
        "properties": {
          "responses": {
            "type": "object",
            "description": "Map Query-ID auf Ergebnis.",
            "additionalProperties": {
              "$ref": "#/components/schemas/QueryResult"
            }
          },
          "matcher": {
            "type": "string"
          },
          "matcher_backend": {
            "type": "string"
          },
          "config_hash": {
            "type": "string"
          },
          "list_versions": {
            "type": "object",
            "additionalProperties": true,
            "description": "Quelle auf Version, Stand, `content_hash`."
          },
          "stale": {
            "type": "boolean",
            "description": "Eine Pflichtliste ist nicht frisch."
          },
          "list_freshness": {
            "type": "object",
            "additionalProperties": true
          },
          "checked_against": {
            "$ref": "#/components/schemas/CheckedAgainst"
          },
          "suppressed": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "alert_policy": {
            "$ref": "#/components/schemas/AlertPolicy"
          },
          "record_count": {
            "type": "integer"
          },
          "threshold": {
            "type": "number"
          },
          "limit": {
            "type": "integer"
          },
          "latency_ms": {
            "type": "integer"
          },
          "request_id": {
            "type": "string",
            "description": "Schlüssel für `/v1/evidence/{request_id}`."
          },
          "audited_at": {
            "type": "string",
            "format": "date-time"
          },
          "effective_thresholds": {
            "type": "object",
            "additionalProperties": true
          },
          "evidence": {
            "$ref": "#/components/schemas/Evidence"
          },
          "confidence_bands": {
            "type": "object",
            "additionalProperties": true
          },
          "disclaimer": {
            "type": "string"
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          },
          "screening_roles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "customer_country_risk": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "tenant_id": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "examples": [
                "match",
                "batch",
                "cases",
                "monitor",
                "lists",
                "keys:manage"
              ]
            }
          },
          "quota_per_day": {
            "type": "integer"
          },
          "quota_per_minute": {
            "type": "integer"
          },
          "key_prefix": {
            "type": "string"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "MonitorCreate": {
        "type": "object",
        "description": "Monitor (Einzelabfrage). `query` oder `name` ist Pflicht. `cadence`: `on_list_update`, `hourly`, `daily`, `weekly`.",
        "properties": {
          "name": {
            "type": "string"
          },
          "query": {
            "type": "string"
          },
          "entity_ref": {
            "type": "string"
          },
          "schema": {
            "type": "string"
          },
          "birth_year": {
            "type": "integer"
          },
          "country": {
            "type": "string"
          },
          "datasets": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "threshold": {
            "type": "number",
            "default": 0.85
          },
          "cadence": {
            "type": "string",
            "enum": [
              "on_list_update",
              "hourly",
              "daily",
              "weekly"
            ],
            "default": "on_list_update"
          },
          "active": {
            "type": "boolean"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri"
          },
          "webhook_secret": {
            "type": "string",
            "writeOnly": true
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "PositiveHistory": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "seq": {
            "type": "integer"
          },
          "state": {
            "$ref": "#/components/schemas/TriageState"
          },
          "version": {
            "type": "integer"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "action": {
                  "type": "string"
                },
                "from_state": {
                  "type": "string"
                },
                "to_state": {
                  "type": "string"
                },
                "reviewer": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reason_code": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reason_text": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "version": {
                  "type": "integer"
                },
                "at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "PositiveRow": {
        "type": "object",
        "description": "Eine Person (Treffer-Vertrag §4). Im Detail zusätzlich `hits[]` und `suppressed_hits[]`.",
        "properties": {
          "seq": {
            "type": "integer"
          },
          "query": {
            "type": "object",
            "description": "Aus der Anlieferung.",
            "properties": {
              "name": {
                "type": "string"
              },
              "birth_year": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "schema": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "top_band": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ConfidenceBand"
              },
              {
                "type": "null"
              }
            ],
            "description": "Stärkstes Band über alle Treffer; `null` bei reinen Hinweis-Zeilen."
          },
          "top_hit": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Hit"
              },
              {
                "type": "null"
              }
            ]
          },
          "hit_count": {
            "type": "integer",
            "description": "Zusammengeführte Personen."
          },
          "record_count": {
            "type": "integer"
          },
          "categories_top": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "categories_other": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources_top": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources_other": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "suppressed": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "alert": {
            "type": "boolean",
            "description": "`false` bei Hinweis-Zeilen."
          },
          "hint_count": {
            "type": "integer"
          },
          "hint_reasons": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "disposition": {
            "type": "string",
            "enum": [
              "open",
              "false_positive",
              "confirmed"
            ]
          },
          "state": {
            "$ref": "#/components/schemas/TriageState"
          },
          "version": {
            "type": "integer"
          },
          "reviewer": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "hits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HitSummary"
            }
          },
          "suppressed_hits": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/Hit"
            }
          },
          "result_version": {
            "type": "integer"
          }
        }
      },
      "PositivesPage": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "count": {
            "type": "integer"
          },
          "next_offset": {
            "type": [
              "integer",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PositiveRow"
            }
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Fehler nach RFC 9457 (`application/problem+json`) mit stabilem `code`. Fehlerantworten tragen immer `X-Request-ID`.",
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Stabile Kennung `urn:koivu:problem:<code>`."
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stabiler Code, snake_case. Katalog siehe `info.x-koivu-problem-catalog`.",
            "enum": [
              "validation_failed",
              "unauthorized",
              "forbidden_scope",
              "ip_not_allowed",
              "forbidden",
              "not_found",
              "method_not_allowed",
              "conflict",
              "idempotency_in_progress",
              "idempotency_key_reused",
              "payload_too_large",
              "unsupported_format",
              "rate_limited",
              "quota_exceeded",
              "list_stale_mandatory",
              "database_unavailable",
              "service_unavailable",
              "internal_error"
            ]
          },
          "request_id": {
            "type": "string"
          },
          "retry_after": {
            "type": "integer",
            "description": "Sekunden"
          },
          "errors": {
            "type": "array",
            "description": "Eingabefehler je Feld.",
            "items": {
              "type": "object",
              "required": [
                "pointer",
                "code"
              ],
              "properties": {
                "pointer": {
                  "type": "string",
                  "description": "JSON-Pointer auf das Feld"
                },
                "code": {
                  "type": "string"
                },
                "detail": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "QueryResult": {
        "type": "object",
        "description": "Ergebnis einer Query.",
        "properties": {
          "status": {
            "type": "integer",
            "examples": [
              200
            ]
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Hit"
            }
          },
          "suppressed_hits": {
            "type": "array",
            "maxItems": 20,
            "description": "Von den Alarm-Regeln ausgeblendete Hinweise (gleiche Form wie `Hit`). Zählen **nicht** als Treffer.",
            "items": {
              "$ref": "#/components/schemas/Hit"
            }
          },
          "hit_count": {
            "type": "integer",
            "description": "Zusammengeführte Personen vor der Kappung durch `limit`."
          },
          "record_count": {
            "type": "integer",
            "description": "Rohdatensätze hinter den Treffern."
          },
          "suppressed": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Zähler, z. B. `deceased_pep`, `phonetic_only`, `over_limit`."
          },
          "more_available": {
            "type": "boolean"
          },
          "alert_policy": {
            "$ref": "#/components/schemas/AlertPolicy"
          },
          "total": {
            "type": "object",
            "properties": {
              "value": {
                "type": "integer"
              },
              "relation": {
                "type": "string"
              }
            }
          },
          "query": {
            "type": "object",
            "additionalProperties": true
          },
          "context_signals": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Nur mit `include_context=true`; `context_only=true`, `person_linked=false`."
          },
          "disclaimer": {
            "type": "string"
          }
        }
      },
      "SourcesCatalog": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "country": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "category": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "list_date": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "list_date_basis": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "published",
                    "fetched",
                    null
                  ]
                },
                "entity_count": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "decision": {
            "type": "string",
            "const": "signal_only"
          },
          "true_match": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "TriageState": {
        "type": "string",
        "enum": [
          "open",
          "proposed_false_positive",
          "proposed_confirmed",
          "escalated",
          "false_positive",
          "confirmed"
        ],
        "description": "Bearbeitungsstand einer Person im Lauf. Der Entscheid bleibt Handlung der Kundin."
      },
      "UploadAccepted": {
        "type": "object",
        "required": [
          "ok",
          "job_id",
          "total",
          "mode",
          "poll"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "total": {
            "type": "integer"
          },
          "mode": {
            "type": "string",
            "const": "async"
          },
          "threshold": {
            "type": "number"
          },
          "limit": {
            "type": "integer"
          },
          "poll": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string"
          },
          "matcher": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string"
          }
        }
      },
      "HitSummary": {
        "type": "object",
        "description": "Kurzform eines Treffers in Tabellenzeilen. `explain`, `source`, `records`, `features` sind hier `null`; die Volldaten stehen in `top_hit` und im Detail der Person.",
        "required": [
          "entity_id",
          "caption",
          "score",
          "confidence_band"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Heute identisch zu `entity_id` (Altfeld, bleibt)."
          },
          "entity_id": {
            "type": "string",
            "examples": [
              "ch-seco-12345"
            ]
          },
          "match": {
            "type": "boolean",
            "description": "Altfeld des Matchers (Schwelle erreicht). Kein Entscheid."
          },
          "true_match": {
            "type": "boolean",
            "const": false,
            "description": "Immer `false`: Koivu stellt nie einen Treffer als bestätigt fest."
          },
          "score_adjusted": {
            "type": "number",
            "description": "Altfeld, Score nach Boni."
          },
          "profile_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Zusammenführungs-Schlüssel (Treffer-Vertrag §2)."
          },
          "also_on": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Weitere Quellen derselben Person (Altfeld)."
          },
          "risk_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Risikomodell (Zusatz, additiv); `risk_band`, `risk_reasons`, `risk_contributions`, `risk_model_version` folgen demselben Muster."
          },
          "risk_band": {
            "type": [
              "string",
              "null"
            ]
          },
          "caption": {
            "type": "string",
            "description": "Anzeigename."
          },
          "caption_original": {
            "type": [
              "string",
              "null"
            ],
            "description": "Originalschreibweise, wenn abweichend."
          },
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1.5,
            "description": "Ranking-Score; darf Boni enthalten (Geburtsjahr +0,05, ID +0,10). Die Stufe hängt nicht an den Boni."
          },
          "confidence_band": {
            "$ref": "#/components/schemas/ConfidenceBand"
          },
          "explain": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/HitExplain"
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/HitSource"
              },
              {
                "type": "null"
              }
            ]
          },
          "records": {
            "oneOf": [
              {
                "type": "array",
                "description": "Alle Listeneinträge dieser gelisteten Person.",
                "items": {
                  "type": "object",
                  "properties": {
                    "source_id": {
                      "type": "string"
                    },
                    "entity_id": {
                      "type": "string"
                    },
                    "list_version": {
                      "type": "string"
                    },
                    "url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "deceased": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`null` = unbekannt (nie `false`, wenn nicht berechnet)."
          },
          "death_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "deceased_basis": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "wikidata_p570"
            ]
          },
          "pep_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "pep_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_rca": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "features": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "birth_date": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "birth_year": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "countries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "nationality": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "aliases": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      }
    }
  }
}
