Meldefälle (NIS-2)
Einen NIS-2-Meldefall führen: Kriterien, Entscheidung zur Meldepflicht, Zeitpunkt der Kenntnis, Fristen, Berichtsentwürfe und das Protokoll Ihrer Abgaben im BSI-Portal.
GET /api/im/report-cases · POST /api/im/report-cases · GET /api/im/report-cases/:id · PATCH /api/im/report-cases/:id
POST /api/im/report-cases/:id/aware-at · POST /api/im/report-cases/:id/criteria · POST /api/im/report-cases/:id/decision · POST /api/im/report-cases/:id/interim-request · POST /api/im/report-cases/:id/close
POST /api/im/report-cases/:id/incidents · DELETE /api/im/report-cases/:id/incidents/:incidentId · GET /api/im/report-cases/:id/draft · POST /api/im/report-cases/:id/submissions
Ein Meldefall sammelt, was Sie für eine NIS-2-Meldung brauchen (Art. 23 NIS-2-Richtlinie, § 32 BSIG): die verknüpften Vorfälle, je Erheblichkeitskriterium eine Entscheidung, die Entscheidung zur Meldepflicht, den Zeitpunkt der Kenntnis und die Fristen daraus (Frühwarnung 24 h, Meldung 72 h, Abschlussmeldung einen Monat nach der Meldung, Zwischenmeldung auf Anfrage). Jede Meldung geben Sie selbst im BSI-Portal ab. Uptimeify übermittelt nichts und stuft keinen Vorfall selbst ein. Kriterien, Abgaben und Zeitachse sind nur anfügbar: nachträglich lässt sich nichts ändern oder löschen. Geschlossene Fälle werden drei Jahre aufbewahrt.
:id ist die publicId des Falls oder seine numerische id. :incidentId ist die numerische ID eines IM-Vorfalls.
Authentifizierung
Nur mit Sitzung (Dashboard-Anmeldung); API-Tokens werden mit 403 (nis2OfficerRequired) abgewiesen. NIS-2-Meldefälle müssen eingeschaltet sein (NIS-2-Einstellungen). Organisations-Admins und eingetragene Meldeverantwortliche dürfen lesen und schreiben, alle anderen bekommen 403. Ein Fall einer anderen Organisation antwortet 404 (reportCaseNotFound), genau wie ein fehlender.
Meldefälle auflisten
GET /api/im/report-cases
| Query | Beschreibung |
|---|---|
status | Kommagetrennter Statusfilter, z. B. reportable,early_warning_submitted. |
page | Standard 1. |
perPage | Standard 50, höchstens 200. |
Sortiert nach der nächsten offenen Frist; Fälle ohne Frist stehen am Ende, neueste zuerst.
curl "$BASE_URL/api/im/report-cases?status=reportable&perPage=20" -H "Cookie: $SESSION_COOKIE"200 OK
{
"items": [
{
"id": 12,
"publicId": "0b6f9a52-7a43-4c8e-9f1e-5c2d8a1b3e77",
"title": "Ausfall Kundenportal",
"status": "reportable",
"awareAt": "2026-10-21T07:00:00.000Z",
"createdAt": "2026-10-21T07:05:00.000Z",
"nextDeadline": { "stage": "early_warning", "dueAt": "2026-10-22T07:00:00.000Z", "met": false },
"incidentCount": 1
}
],
"total": 1,
"page": 1,
"perPage": 20
}nextDeadline ist { stage, dueAt, met } oder null.
Statuswerte: assessment, not_reportable, reportable, early_warning_submitted, notification_submitted, final_submitted, closed.
Meldefall öffnen
POST /api/im/report-cases
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
title | string | Ja | 1 bis 200 Zeichen. |
incidentIds | integer[] | Nein | IM-Vorfälle Ihrer Organisation, höchstens 50, keine Test-Vorfälle. |
awareAt beginnt mit dem Zeitpunkt der Anfrage; korrigieren mit aware-at. Jeder andere Schlüssel ist ein 400.
curl -X POST "$BASE_URL/api/im/report-cases" -H "Cookie: $SESSION_COOKIE" -H "Content-Type: application/json" \
-d '{ "title": "Ausfall Kundenportal", "incidentIds": [4812] }'201 Created: der ganze Fall, gleiche Form wie Meldefall lesen.
Meldefall lesen
GET /api/im/report-cases/:id
200 OK
{
"id": 12,
"publicId": "0b6f9a52-7a43-4c8e-9f1e-5c2d8a1b3e77",
"organizationId": 2,
"title": "Ausfall Kundenportal",
"status": "reportable",
"awareAt": "2026-10-21T07:00:00.000Z",
"decision": "reportable",
"decidedBy": "u_8f2c",
"decidedAt": "2026-10-21T08:10:00.000Z",
"decisionReason": null,
"suspectedMalicious": true,
"crossBorder": false,
"severityAssessment": "high",
"impact": "Kundenportal nicht erreichbar",
"affectedUsers": null,
"iocs": "203.0.113.7",
"rootCause": null,
"mitigation": null,
"kritisFacility": null,
"criticalService": null,
"interimRequestedAt": null,
"interimDueAt": null,
"createdBy": "u_8f2c",
"createdAt": "2026-10-21T07:05:00.000Z",
"closedAt": null,
"canDecide": true,
"nextDeadline": { "stage": "early_warning", "dueAt": "2026-10-22T07:00:00.000Z", "met": false },
"deadlines": [
{ "stage": "early_warning", "dueAt": "2026-10-22T07:00:00.000Z", "met": false },
{ "stage": "notification", "dueAt": "2026-10-24T07:00:00.000Z", "met": false }
],
"incidents": [
{ "id": 4812, "title": "Portal weg", "severity": "sev1", "status": "resolved", "triggeredAt": "2026-10-21T06:40:00.000Z", "resolvedAt": "2026-10-21T07:27:00.000Z" }
],
"criteria": [
{ "key": "total_outage_over_30min", "suggestible": true, "suggestedValue": true, "suggestionSource": "monitoring:47min", "decision": "confirmed", "value": true, "decidedBy": "u_8f2c", "decidedAt": "2026-10-21T08:05:00.000Z" }
],
"submissions": [],
"events": [
{ "id": 301, "type": "decided", "actorUserId": "u_8f2c", "payload": { "decision": "reportable", "reason": null, "previous": null }, "createdAt": "2026-10-21T08:10:00.000Z" }
]
}Felder ohne Wert sind null, nie weggelassen.
criteria enthält immer alle zehn Kriterien (hier gekürzt): total_outage_over_30min, degraded_over_1h_5pct_users, financial_loss_500k_or_5pct, trade_secret_exfiltration, death_or_health_damage, malicious_unauthorized_access, iva_compromised_malicious, iva_compromised_5pct_users, third_party_damage, planned_maintenance. Nur das erste und das letzte bekommen einen Vorschlag aus den Monitoring-Daten; ein Vorschlag ist nie eine Entscheidung. deadlines bleibt leer, bis der Fall als reportable entschieden ist. events ist die nur anfügbare Zeitachse, neueste zuerst.
Inhaltsfelder ändern
PATCH /api/im/report-cases/:id
Merge-Patch über die Inhaltsfelder: suspectedMalicious, crossBorder (boolean oder null), severityAssessment, impact, affectedUsers, iocs, rootCause, mitigation, kritisFacility, criticalService (Text bis 10000 Zeichen oder null). Nicht genannte Felder bleiben, null leert, leerer Text wird null. Jeder andere Schlüssel ist ein 400. 200 OK: der ganze Fall.
Aktionen am Fall
Jede antwortet 200 OK mit dem ganzen Fall. Jeder andere Schlüssel im Body ist ein 400.
| Aktion | Body | Regel |
|---|---|---|
POST /api/im/report-cases/:id/aware-at | awareAt (ISO-Zeitpunkt, Pflicht), reason (Text oder null) | Früher ist frei; später braucht eine reason (reportCaseAwareAtReasonRequired). |
POST /api/im/report-cases/:id/criteria | key (eines der zehn), decision (confirmed | rejected), value (boolean), alle Pflicht | Fügt eine neue Entscheidung an; die jüngste gilt. |
POST /api/im/report-cases/:id/decision | decision (reportable | not_reportable, Pflicht), reason | Erst wenn jedes Kriterium entschieden ist (reportCaseCriteriaOpen). not_reportable braucht eine reason und geht nach einer Abgabe nicht mehr. |
POST /api/im/report-cases/:id/interim-request | requestedAt, dueAt (ISO-Zeitpunkte, Pflicht) | Das BSI hat eine Zwischenmeldung angefordert; dueAt muss nach requestedAt liegen. |
POST /api/im/report-cases/:id/close | keiner | Erst nach der Abschlussmeldung oder bei not_reportable (reportCaseCannotClose). Ein geschlossener Fall nimmt keine Änderung mehr an. |
curl -X POST "$BASE_URL/api/im/report-cases/0b6f9a52-7a43-4c8e-9f1e-5c2d8a1b3e77/decision" \
-H "Cookie: $SESSION_COOKIE" -H "Content-Type: application/json" -d '{ "decision": "reportable" }'Vorfälle verknüpfen und lösen
POST /api/im/report-cases/:id/incidents mit { "incidentId": 4813 } antwortet 201 Created:
{ "reportCaseId": 12, "reportCasePublicId": "0b6f9a52-7a43-4c8e-9f1e-5c2d8a1b3e77", "incidentId": 4813, "linkedAt": "2026-10-21T09:00:00.000Z" }DELETE /api/im/report-cases/:id/incidents/:incidentId antwortet 204 No Content. Ein zweites DELETE antwortet 404 (reportCaseIncidentNotLinked). Verknüpfen und Lösen aktualisieren die Vorschläge aus dem Monitoring.
Berichtsentwurf
GET /api/im/report-cases/:id/draft?stage=early_warning&locale=de
stage (Pflicht): early_warning, notification, interim, progress oder final. locale: de (Standard) oder en. Fehlende Pflichtangaben erscheinen als [fehlt] / [missing].
200 OK
{
"text": "# Frühwarnung (≤ 24 h)\n…",
"draftToken": "v1.1761058800000.de.Qk9…"
}text ist Markdown zum Kopieren ins BSI-Portal. draftToken ist die Signatur des Servers über genau diesen Text, diesen Fall, diese Stufe und diese Sprache. Behandeln Sie ihn als undurchsichtig, bewahren Sie ihn zusammen mit dem Text auf und schicken Sie beides beim Protokollieren der Abgabe zurück. Er gilt 90 Tage.
Abgabe protokollieren
POST /api/im/report-cases/:id/submissions
Der Ablauf: Entwurf kopieren (text und draftToken), text im BSI-Portal abgeben, danach hier die Abgabe mit demselben text und draftToken protokollieren. Der Server friert genau diesen Text ein. Weil der Token beweist, dass der Server ihn erzeugt hat, ist der eingefrorene Text zugleich das, was Sie wirklich abgegeben haben, und nichts, was Sie hätten erfinden können. Ein späteres Bearbeiten des Falls ändert den Beleg nicht: ein späterer Entwurf ist ein neuer Text mit neuem Token.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
stage | string | Ja | early_warning, notification, interim, progress oder final. Muss die Stufe des Entwurfs sein. |
submittedAt | string (ISO-Zeitpunkt) | Ja | Wann Sie im BSI-Portal abgegeben haben; nicht in der Zukunft. |
reference | string | Ja | Aktenzeichen aus dem BSI-Portal, 1 bis 200 Zeichen. |
text | string | Ja | Der Entwurfstext genau so, wie GET .../draft ihn geliefert hat, bis 100000 Zeichen. Kein Zeichen darf abweichen. |
draftToken | string | Ja | Der draftToken, der mit diesem Text kam. Die Sprache steckt im Token. |
Gehören Text und Token nicht zusammen (anderer Text, Fall oder andere Stufe, oder der Token ist älter als 90 Tage), antwortet der Server 400 (reportCaseDraftInvalid): Entwurf neu holen und diesen abgeben. Nur bei Fällen, die als reportable entschieden sind. Die Stufen folgen der Reihenfolge des Gesetzes: einmal early_warning, dann notification (zählt auch als Frühwarnung, wenn keine erfasst ist), danach beliebig viele interim und progress, zuletzt final. Jede andere Reihenfolge antwortet 409 (reportCaseInvalidTransition). Jeder andere Schlüssel ist ein 400.
curl -X POST "$BASE_URL/api/im/report-cases/0b6f9a52-7a43-4c8e-9f1e-5c2d8a1b3e77/submissions" \
-H "Cookie: $SESSION_COOKIE" -H "Content-Type: application/json" \
-d '{ "stage": "early_warning", "submittedAt": "2026-10-21T15:00:00Z", "reference": "BSI-2026-4711", "text": "# Frühwarnung (≤ 24 h)\n…", "draftToken": "v1.1761058800000.de.Qk9…" }'201 Created
{ "id": 41, "stage": "early_warning", "submittedAt": "2026-10-21T15:00:00.000Z", "submittedBy": "u_8f2c", "reference": "BSI-2026-4711", "frozenText": "# Frühwarnung (≤ 24 h)\n…", "createdAt": "2026-10-21T15:02:00.000Z" }Häufige Fehler
401 Unauthorized(unauthorized) ohne Anmeldung403 Forbidden(imAccessDenied) für einen Kunden-Login oder eine Rolle ohne Incident-Management-Zugang403 Forbidden(nis2NotEnabled), wenn Meldefälle ausgeschaltet sind403 Forbidden(nis2OfficerRequired) für ein API-Token oder eine Person, die weder Admin noch Meldeverantwortliche ist400 Bad Request(invalidIdentifier), wenn:idweder Zahl noch UUID ist404 Not Found(reportCaseNotFound), wenn der Fall nicht existiert oder einer anderen Organisation gehört400 Bad Request(invalidRequestBody) bei fehlendem Feld, falschem Typ, unbekanntem Schlüssel, leererreference, ungültigem Query-Wert oderdueAtnicht nachrequestedAt404 Not Found(imIncidentNotFound), wenn ein Vorfall nicht existiert oder einer anderen Organisation gehört400 Bad Request(reportCaseTestIncident), (reportCaseReasonRequired), (reportCaseAwareAtReasonRequired), (reportCaseSubmittedInFuture), (reportCaseDraftInvalid)409 Conflict(reportCaseClosed), (reportCaseCriteriaOpen), (reportCaseAlreadySubmitted), (reportCaseNotReportable), (reportCaseCannotClose), (reportCaseIncidentAlreadyLinked), (reportCaseInvalidTransition)404 Not Found(reportCaseIncidentNotLinked) beim Lösen eines Vorfalls, der nicht verknüpft ist
Ausgehende Integrationen
Incident-Management-Ereignisse an Slack, Microsoft Teams, Discord, Jira, PagerDuty, Opsgenie oder einen generischen Webhook weiterleiten, Zustellungen einsehen und erneut senden.
Berichte
Incident-Bericht (Volumen, MTTA, MTTR nach Team, Schweregrad oder Monat) und On-Call-Bericht (Minuten pro Person) für einen Zeitraum, als JSON oder CSV.