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.
Endpoints
Section titled “Endpoints”| 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.
Lifecycle walkthrough
Section titled “Lifecycle walkthrough”1. Create a case
Section titled “1. Create a case”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": "…", … }2. Submit the 24h early warning
Section titled “2. Submit the 24h early warning”Only the obligatory fields for this stage are required (see Obligation Matrix).
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"] }'3. Submit the 72h update
Section titled “3. Submit the 72h update”Fields from the 24h warning (title, CVE, manufacturer…) are carried forward automatically — you only send what’s new.
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" }'4. Submit the final report
Section titled “4. Submit the final report”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.
Validation errors
Section titled “Validation errors”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.
Error codes
Section titled “Error codes”| 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 |
Try it locally
Section titled “Try it locally”Run the module standalone (no auth) for exploration:
DATABASE_URL=postgres://you@localhost/fleet_test \ cargo run -p enisa-srp --example serve# http://127.0.0.1:8099/swagger-ui · /openapi.json