Skip to content

ENISA Reporting — REST API & Swagger

The ENISA SRP module exposes a versioned OpenAPI 3.1 REST API with a bundled Swagger UI, all under /api/v1/enisa. The OpenAPI document and the routes are generated from the same handlers (via utoipa), so they never drift.

Path Method Auth Purpose
/api/v1/enisa/notifications POST ✅ Create a notification case (vulnerability or incident)
/api/v1/enisa/notifications GET ✅ List cases
/api/v1/enisa/notifications/{id} GET ✅ Case with full revision history
/api/v1/enisa/notifications/{id}/revisions POST ✅ Submit a staged revision (validated)
/api/v1/enisa/notifications/{id}/revisions GET ✅ Revision history
/api/v1/enisa/obligations GET ✅ The versioned obligation matrix
/api/v1/enisa/openapi.json GET — OpenAPI 3.1 document
/api/v1/enisa/swagger-ui GET — Swagger UI
/api/v1/enisa/graphql POST ✅ GraphQL queries (see GraphQL)
/api/v1/enisa/graphql/playground GET — GraphQL playground

The documentation surface (Swagger UI, OpenAPI doc, GraphQL playground) is public so it loads in a browser; the data surface requires a FLEET API key.

Authentication, scopes & the reporter identity

Section titled “Authentication, scopes & the reporter identity”

Data endpoints use FLEET’s standard API-key auth:

Authorization: Bearer fleet_xxxxxxxx…

Each request is checked for the appropriate scope:

Operation Required scope
Reads (GET …, and GraphQL queries) enisa:read
Writes (create case, submit revision) enisa:write

A wildcard (*) key satisfies either. A valid key with the wrong scope gets 403 (Missing required scope: …); a missing/invalid key gets 401.

The full catalogue of selectable scopes (including enisa:read/enisa:write) is available at GET /api/v1/admin/scopes (requires admin:read), or via the fleet_list_scopes MCP tool — use it when minting keys with fleet_create_api_key or POST /api/v1/admin/api-keys.

On success, FLEET injects the authenticated key’s name as the reporting entity (field 6, “automated”) via the x-enisa-reporter header. This value overrides any reporter in the request body, so the reporter cannot be spoofed.

Terminal window
curl -X POST https://your-fleet/api/v1/enisa/notifications \
-H "Authorization: Bearer $FLEET_KEY" -H "content-type: application/json" \
-d '{"notification_type":"vulnerability"}'
# → { "id": "…", "notification_type": "vulnerability", "reporter": "…", … }

Only the obligatory fields for this stage are required (see Obligation Matrix).

Terminal window
curl -X POST https://your-fleet/api/v1/enisa/notifications/$ID/revisions \
-H "Authorization: Bearer $FLEET_KEY" -H "content-type: application/json" \
-d '{
"stage": "early_warning_24h",
"manufacturer_name": "ACME Corp",
"product": "Widget Server",
"title": "Actively exploited RCE in config parser",
"cve_id": "CVE-2026-0001",
"member_states": ["DE", "FR"]
}'

Fields from the 24h warning (title, CVE, manufacturer…) are carried forward automatically — you only send what’s new.

Terminal window
curl -X POST https://your-fleet/api/v1/enisa/notifications/$ID/revisions \
-H "Authorization: Bearer $FLEET_KEY" -H "content-type: application/json" \
-d '{
"stage": "update_72h",
"vuln_nature": "Heap overflow in the configuration parser",
"exploit_nature": "Remote, unauthenticated, via crafted config",
"vuln_corrective_taken": "Hotfix 1.2.3 released",
"vuln_user_measures": "Upgrade to 1.2.3; restrict config sources"
}'
Terminal window
curl -X POST https://your-fleet/api/v1/enisa/notifications/$ID/revisions \
-H "Authorization: Bearer $FLEET_KEY" -H "content-type: application/json" \
-d '{
"stage": "final",
"vuln_measure_available_date": "2026-06-10",
"vuln_severity": "Critical (CVSS 9.8)",
"vuln_impact": "Full host compromise",
"vuln_security_update_details": "Fixed in 1.2.3; backported to 1.1.9"
}'

Stages must not go backwards: submitting an earlier stage than the latest recorded one returns 409 STAGE_REGRESSION.

Required-ness is stage-specific. A missing obligatory field returns 422 with a structured list of violations:

{
"error": {
"code": "VALIDATION_FAILED",
"message": "one or more fields are invalid for this stage",
"violations": [
{ "field_id": "12", "stage": "early_warning_24h",
"kind": "missing_obligatory", "message": "field 12 is required at this stage" }
]
}
}

Submitting a field that belongs to the other notification type (e.g. incident data on a vulnerability case) yields a not_applicable violation.

Status code When
404 NOT_FOUND Unknown notification id
409 STAGE_REGRESSION New stage is earlier than the latest recorded stage
422 VALIDATION_FAILED Obligatory field missing, or field not applicable to the type

Submitted revisions are immutable, so editing happens on a mutable draft — a working copy of the report’s fields you change freely, then promote into an immutable 24h/72h/final revision.

Path Method Purpose
/notifications/{id}/draft POST Create a draft (?seed=true pre-fills from the latest revision)
/notifications/{id}/draft GET Read the current draft
/notifications/{id}/draft PATCH Merge field updates (null clears a field)
/notifications/{id}/draft DELETE Discard the draft
/notifications/{id}/draft/validate POST Check readiness ({valid, violations}) without promoting
/notifications/{id}/draft/promote POST Validate + write an immutable revision, then clear the draft
Terminal window
curl -X POST .../notifications/$ID/draft -H "Authorization: Bearer $K"
curl -X PATCH .../notifications/$ID/draft -H "Authorization: Bearer $K" \
-H 'content-type: application/json' \
-d '{"stage":"early_warning_24h","manufacturer_name":"ACME","product":"Widget","title":"RCE"}'
curl -X POST .../notifications/$ID/draft/validate -H "Authorization: Bearer $K" # {"valid":true,...}
curl -X POST .../notifications/$ID/draft/promote -H "Authorization: Bearer $K" # → immutable revision

The same flow is available over MCP (fleet_enisa_get_draft, fleet_enisa_edit_draft, fleet_enisa_promote_draft).

Run the module standalone (no auth) for exploration:

Terminal window
DATABASE_URL=postgres://you@localhost/fleet_test \
cargo run -p enisa-srp --example serve
# http://127.0.0.1:8099/swagger-ui · /openapi.json