Skip to content

API Reference

The NIS2 Platform exposes a REST API at http://localhost:8000. All routes are prefixed with /api/v1/. Interactive OpenAPI documentation is available at /docs (Swagger UI) and /redoc (ReDoc).

All endpoints return JSON. Two authentication paths are accepted on every protected route:

  • Cookie session (web)access_token httpOnly cookie set by /auth/login or /auth/register. State-changing requests must echo the csrf_token cookie back as the X-CSRF-Token header (double-submit pattern).
  • Bearer token (SDK / CLI)Authorization: Bearer <jwt> header. Mints from /auth/login are valid; the same access token from the cookie can be used here.

Read-only endpoints under scans / findings / assets additionally accept a long-lived API key as Authorization: Bearer nis2_… (no cookie required). Keys are minted via POST /api/v1/api-keys (admin only) and the raw value is shown exactly once. Mutation endpoints (POST / PATCH / DELETE) on those resources still require a session — the audit log + created_by columns want a user identity to attribute the change to.

Authentication

MethodPathDescriptionAuth
POST/api/v1/auth/registerRegister a new user and create an organization. Returns access and refresh tokens (also set as httpOnly cookies for the web client)No
POST/api/v1/auth/loginObtain access and refresh tokensNo
POST/api/v1/auth/refreshRotate the access token using the refresh token cookie. Refresh tokens themselves are single-use (jti tracked in revoked_tokens); reusing one revokes the entire familyNo
POST/api/v1/auth/logoutClear cookies and revoke the refresh tokenYes
GET/api/v1/auth/meGet current user profileYes
PATCH/api/v1/auth/meUpdate current user profile (name / locale / avatar). Does not accept current_password / new_password — see /auth/change-passwordYes
POST/api/v1/auth/change-passwordRotate the user's password. Verifies current_password, hashes new_password, stamps password_changed_at so every other still-active session is invalidated on its next request, re-issues this session's cookies. 5/min/IP rate-limitedYes
POST/api/v1/auth/forgot-passwordKick off the email-based reset flow. Always returns 204 regardless of whether the email exists, so the response can't be used to enumerate registered users. 5/min/IP rate-limitedNo
POST/api/v1/auth/reset-passwordComplete the reset flow with a single-use token (delivered out-of-band by email) and a new password. Tokens are sha256-hashed at rest, expire after RESET_TOKEN_TTL_MINUTES (default 30), and a single 400 covers {unknown, expired, used} — no oracle on which one applies. 10/min/IP rate-limitedNo
POST/api/v1/auth/switch-orgSwitch the active organization for the current session. Body: {"organization_id": "<uuid>"}. Validates the caller has a membership for the target org (403 otherwise — the org may exist but the membership doesn't), then mints fresh access / refresh / csrf tokens with the new org_id claim and rotates the cookies. Returns TokenResponse (same shape as /login). Used by the web client's org-switcher dropdown; SDKs can call it the same way. 10/min/IP rate-limitedYes

Scans

Auth column legend: Session = cookie or Bearer <jwt>. API key = Bearer nis2_… also accepted (no cookie required).

MethodPathDescriptionAuth
GET/api/v1/scansList scans for current organization. Filterable by status. PaginatedSession or API key
POST/api/v1/scansCreate and queue a new scanSession
GET/api/v1/scans/{scan_id}Get scan details and statusSession or API key
DELETE/api/v1/scans/{scan_id}Delete a scan and its findings (admin only)Session
GET/api/v1/scans/{scan_id}/resultsList raw scan results for a scan. PaginatedSession or API key
GET/api/v1/scans/{scan_id}/findingsList findings for a scan. PaginatedSession or API key
POST/api/v1/scans/{scan_id}/cancelCancel a pending or running scanSession
GET/api/v1/scans/{scan_id}/compare/{other_id}Compare two scans: score delta, new/resolved/persistent findingsSession or API key

Findings

MethodPathDescriptionAuth
GET/api/v1/findingsList all findings. Filterable by severity, status, category. PaginatedSession or API key
GET/api/v1/findings/statsGet finding counts grouped by severity and statusSession or API key
GET/api/v1/findings/{finding_id}Get finding detailsSession or API key
PATCH/api/v1/findings/{finding_id}Update finding status or resolution noteSession
POST/api/v1/findings/bulk-updateBulk update status for multiple findingsSession

Assets

MethodPathDescriptionAuth
GET/api/v1/assetsList assets for current organization. PaginatedSession or API key
POST/api/v1/assetsCreate a new assetSession
GET/api/v1/assets/{asset_id}Get asset detailsSession or API key
PATCH/api/v1/assets/{asset_id}Update an assetSession
DELETE/api/v1/assets/{asset_id}Delete an assetSession
POST/api/v1/assets/importImport assets from a CSV fileSession

Schedules

MethodPathDescriptionAuth
GET/api/v1/schedulesList scan schedulesYes
POST/api/v1/schedulesCreate a scan schedule (cron expression). Admin or auditor onlyYes
PATCH/api/v1/schedules/{schedule_id}Update a scheduleYes
DELETE/api/v1/schedules/{schedule_id}Delete a scheduleYes
POST/api/v1/schedules/{schedule_id}/runTrigger an immediate run of a scheduled scanYes

Reports

MethodPathDescriptionAuth
POST/api/v1/reports/generateQueue report generation for a completed scan. Params: scan_id, format (pdf, html, markdown, json, csv, junit). Returns a task_id. In-flight dedup: a duplicate request for the same (organization_id, scan_id, format) triple within ~5 minutes returns the existing task_id with deduplicated: true instead of queuing a new task. The report is rendered in the calling user's locale (user.locale, e.g. it / fr / de / es) — document chrome (titles, table headers, footer) is translated; user-provided fields (scan name, finding messages) pass through unchanged. 5/min/IP rate-limited. The HTML report carries the lang attribute matching the user's locale.Yes
GET/api/v1/reports/status/{task_id}Check report generation status by Celery task IDYes
GET/api/v1/reports/download/{task_id}Download a generated report file by Celery task IDYes

Organizations

MethodPathDescriptionAuth
GET/api/v1/organizationsList organizations the current user belongs toYes
POST/api/v1/organizationsCreate a new organization owned by the current user. Body: {"name": "<string>"} (1..256 chars). The slug is derived server-side from the name with a numeric suffix appended on collision. The caller is automatically added as an accepted_at-stamped admin member. 5/min/IP rate-limited; audit-logged as organization.created. Returns the new OrgResponse with HTTP 201Yes
GET/api/v1/organizations/{org_id}Get organization detailsYes
PATCH/api/v1/organizations/{org_id}Update organization settings (admin only)Yes
GET/api/v1/organizations/{org_id}/membersList organization membersYes
POST/api/v1/organizations/{org_id}/membersInvite a member by email (admin only)Yes
PATCH/api/v1/organizations/{org_id}/members/{member_id}Update a member's role (admin only). Body: {"role": "admin" | "auditor" | "viewer"}. Self-demotion and last-admin demotion are refused with 400Yes
DELETE/api/v1/organizations/{org_id}/members/{member_id}Remove a member (admin only). Cannot remove the last adminYes

Health

MethodPathDescriptionAuth
GET/api/v1/healthLiveness check. Returns {"status": "ok"}No
GET/api/v1/health/readyReadiness check. Tests database and Redis connectivityNo

Certificates

MethodPathDescriptionAuth
POST/api/v1/certificates/checkDeep certificate analysis for a single domain. Returns chain, OCSP, CT logs, key strength, 0-100 scoreYes
POST/api/v1/certificates/bulk-checkAnalyze up to 50 domains at once with summary statisticsYes
GET/api/v1/certificates/ct-logs/{domain}Query Certificate Transparency logs via crt.shYes

Remediation

MethodPathDescriptionAuth
GET/api/v1/remediation/playbooksList all available remediation playbooksNo
GET/api/v1/remediation/playbooks/{id}Get full playbook with steps, configs, and effort estimateNo
GET/api/v1/remediation/for-finding/{finding_id}Auto-match the best playbook for a specific findingYes
GET/api/v1/remediation/estimate/{scan_id}Calculate total remediation effort and cost for a scanYes
POST/api/v1/remediation/explain/{finding_id}AI-powered finding explanation with personalized remediation. Tries local LLM, then OpenAI, then playbook fallbackYes

Incidents

MethodPathDescriptionAuth
POST/api/v1/incidentsReport an incident (CSIRT Art. 23 taxonomy)Yes
GET/api/v1/incidentsList incidents for the organizationYes
GET/api/v1/incidents/{id}Get incident detailsYes
PATCH/api/v1/incidents/{id}Update incident status or detailsYes

Governance

MethodPathDescriptionAuth
GET/api/v1/governance/checklistGet the 30-item NIS2 governance checklist with statusesYes
PATCH/api/v1/governance/checklist/{item_id}Update checklist item statusYes
POST/api/v1/governance/seedSeed checklist from governance templateYes
GET/api/v1/governance/scoreGet weighted compliance scoreYes

API Keys

API keys are long-lived Bearer tokens (nis2_* prefix) for CI/CD pipelines and SDK consumers. The raw value is shown exactly once in the create response — store it securely; only the prefix and sha256 hash are kept server-side. Keys honor expires_at (the route flips is_active=False on first expired use, so the list view stays consistent without a sweeper cron). Successful authentications stamp last_used_at.

Keys authenticate the read endpoints of scans / findings / assets (see those sections above). Mutation endpoints still require a session.

MethodPathDescriptionAuth
GET/api/v1/api-keysList API keys for the organization (admin or auditor)Yes
POST/api/v1/api-keysCreate a new API key (admin only). Response includes raw_key — shown onceYes
DELETE/api/v1/api-keys/{key_id}Revoke an API key (admin only)Yes

Audit Log

Every state-changing action under organizations / api-keys / auth (and increasingly elsewhere) writes a row to the audit log. Reads are paginated and filterable; retention is 90 days.

MethodPathDescriptionAuth
GET/api/v1/audit-logsList audit entries for the organization (admin or auditor). Filterable by action, resource_type, user_id. Paginated. Hydrates actor (user) email + name in one batch queryYes

Vendors (Art. 18 Supply Chain)

MethodPathDescriptionAuth
GET/api/v1/vendorsList vendors for the organization, ordered by criticalityYes
POST/api/v1/vendorsRegister a new vendor/supplierYes
GET/api/v1/vendors/statsSupply chain risk overview: distribution by criticality, type, locationYes
GET/api/v1/vendors/{vendor_id}Get vendor detailsYes
PATCH/api/v1/vendors/{vendor_id}Update vendor details, status, or security assessmentYes
DELETE/api/v1/vendors/{vendor_id}Remove a vendorYes

Business Impact Analysis (BIA)

MethodPathDescriptionAuth
GET/api/v1/biaList business processes for the organizationYes
POST/api/v1/biaRegister a business process for BIAYes
GET/api/v1/bia/matrixBIA impact matrix with automatic gap detectionYes
GET/api/v1/bia/{process_id}Get business process detailsYes
DELETE/api/v1/bia/{process_id}Remove a business processYes

ACN Export (Italy)

MethodPathDescriptionAuth
GET/api/v1/acn-export/art18Export Art. 18 vendor inventory in ACN-compatible JSONYes
GET/api/v1/acn-export/biaExport BIA data in ACN-compatible JSONYes

Compliance Deadlines

MethodPathDescriptionAuth
GET/api/v1/deadlinesNIS2 compliance timeline with countdown, urgency classification, and days remainingNo

CSIRT Emergency (Art. 23)

MethodPathDescriptionAuth
POST/api/v1/csirt/emergencyGenerate Art. 23 Early Warning payload from minimal input (3 fields). Uses latest asset inventoryYes

MCP (Model Context Protocol)

MethodPathDescriptionAuth
GET/api/v1/mcp/toolsList available MCP toolsNo
POST/api/v1/mcp/callExecute an MCP tool call via HTTPNo

Error Responses

All errors follow a consistent format:

json
{
  "detail": "Description of the error"
}

Common HTTP status codes:

CodeMeaning
400Bad request (validation error)
401Unauthorized (missing or invalid token)
403Forbidden (insufficient role permissions)
404Resource not found
409Conflict (duplicate resource)
422Unprocessable entity (invalid request body)
429Too many requests (rate limited)
500Internal server error