# NIS2 Agile API

API REST per integrare NIS2 Agile con SIEM, GRC, piattaforme ESG e strumenti di compliance.

v1.0.0 — Produzione: https://nis2.agile.software/

## Panoramica

NIS2 Agile espone due famiglie di API: Services API per lettura dati di compliance in tempo reale, e Webhook API per notifiche push su eventi NIS2.

Base URL: https://nis2.agile.software/api
Rate Limiting: Services API: 100 richieste/ora per API Key. Webhook delivery: max 1000/ora per subscription.
Formato risposta: Tutte le risposte sono JSON con envelope `{ "success": bool, "data": {}, "message": "..." }`

## Autenticazione

Le Services API usano API Keys con scope granulari. Le API di gestione (webhook, key management) usano JWT Bearer token della sessione utente.

### API Key — 3 modalità

    # Header (raccomandato)
    X-API-Key: nis2_abc123def456...

    # Authorization Bearer
    Authorization: Bearer nis2_abc123def456...

    # Query string (sconsigliato in prod)
    GET /api/services/risks-feed?api_key=nis2_abc123def456...

### JWT Bearer — Per gestione API

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

### Scope disponibili

read:all
Accesso completo in lettura a tutti i dati
read:compliance
Compliance score e controlli Art.21
read:risks
Risk register e matrice rischi ISO 27005
read:incidents
Incidenti e timeline Art.23
read:assets
Inventario asset critici
read:supply_chain
Supply chain e rischio fornitori
read:policies
Policy approvate

### Header obbligatorio

    X-Organization-Id: 42   # ID organizzazione target

## Gestione Errori

HTTP | error_code | Descrizione
--- | --- | ---
400 | `VALIDATION_ERROR` | Parametri obbligatori mancanti o non validi
401 | `UNAUTHORIZED` | API Key assente, scaduta o non valida
403 | `INSUFFICIENT_SCOPE` | Scope insufficiente per questa risorsa
404 | `NOT_FOUND` | Risorsa non trovata
429 | `RATE_LIMITED` | Limite richieste superato
500 | `INTERNAL_ERROR` | Errore interno del server

    {
      "success": false,
      "message": "Scope insufficiente: richiesto read:risks",
      "error_code": "INSUFFICIENT_SCOPE"
    }

## Services API

Endpoint GET per leggere dati di compliance da sistemi esterni. Autenticazione tramite API Key con scope granulari.

GET /services/status Public Health check piattaforma

Stato della piattaforma, versione API e timestamp. Non richiede autenticazione.

#### Risposta 200

    {
      "status": "ok",
      "version": "1.0.0",
      "timestamp": 1709900400,
      "platform": "NIS2 Agile"
    }

GET /services/compliance-summary read:compliance

Score di conformità NIS2 aggregato per dominio Art.21, con statistiche rischi, incidenti aperti e policy approvate.

#### Risposta 200

    {
      "overall_score": 73,
      "label": "substantially_compliant",
      "domain_scores": [
        { "category": "governance", "score": 80, "answered": 8, "total": 10 }
      ],
      "risks": { "total": 24, "critical": 2, "high": 5 },
      "incidents": { "open": 3, "significant": 1 },
      "policies": { "approved": 12, "draft": 4 }
    }

GET /services/risks-feed read:risks

Feed rischi con filtri su livello, area NIS2, stato e data. Include deadline di trattamento.

#### Query Parameters

Parametro | Tipo | Descrizione
--- | --- | ---
`level` | `string` | Filtro livello: `critical`, `high`, `medium`, `low`
`area` | `string` | Categoria rischio (es. `network_security`)
`status` | `string` | Stato: `open`, `in_treatment`, `closed`
`from` | `datetime` | Filtra da data ISO (es. `2026-01-01T00:00:00`)
`limit` | `integer` | Max risultati (default 50, max 200)

#### Risposta 200

    {
      "risks": [
        {
          "id": 15,
          "risk_code": "RSK-2026-015",
          "title": "Vulnerabilità VPN senza MFA",
          "category": "network_security",
          "risk_level": "high",
          "risk_score": 15,
          "treatment": "mitigate",
          "nis2_article": "21.2.i",
          "created_at": "2026-01-15T10:30:00+01:00"
        }
      ],
      "total": 24, "filters": { "level": "high" }
    }

GET /services/incidents-feed read:incidents

Feed incidenti con status Art.23 (deadlines 24h/72h/30d) e flag di scadenza. Utile per integrare in SIEM e SOC dashboard.

#### Query Parameters

Parametro | Tipo | Descrizione
--- | --- | ---
`status` | `string` | `open`, `investigating`, `resolved`, `closed`
`severity` | `string` | `critical`, `high`, `medium`, `low`
`significant_only` | `boolean` | Solo incidenti Art.23 significativi
`from` | `datetime` | Filtra da data ISO

#### Risposta 200 — Elemento

    {
      "id": 7,
      "incident_code": "INC-2026-007",
      "title": "Accesso non autorizzato sistema ERP",
      "severity": "high",
      "is_significant": true,
      "art23_status": {
        "early_warning": { "due": "2026-02-21T14:00:00+01:00", "sent": true, "overdue": false },
        "notification": { "due": "2026-02-23T14:00:00+01:00", "sent": false, "overdue": false },
        "final_report": { "due": "2026-03-22T14:00:00+01:00", "sent": false, "overdue": false }
      }
    }

GET /services/controls-status read:compliance

Stato dei controlli di sicurezza Art.21 raggruppati per categoria (governance, network_security, access_control, ecc.).

#### Risposta 200 — Elemento categoria

    {
      "category": "network_security",
      "total": 8,
      "implemented": 5,
      "partial": 2,
      "not_implemented": 1,
      "score": 75
    }

GET /services/assets-critical read:assets

Inventario asset critici con tipo, livello di criticità e dipendenze. Filtrabili per tipo e livello.

#### Query Parameters

Parametro | Tipo | Descrizione
--- | --- | ---
`type` | `string` | `server`, `network`, `software`, `data`, `service`
`criticality` | `string` | `critical`, `high`, `medium`, `low`

GET /services/suppliers-risk read:supply_chain

Panoramica rischio fornitori con risk_score, data ultima valutazione e flag critici. Include stats aggregate.

#### Risposta 200 — Stats

    {
      "suppliers": [...],
      "stats": {
        "total": 18,
        "critical": 2,
        "high": 4,
        "avg_risk_score": 42
      }
    }

GET /services/policies-approved read:policies

Policy approvate con categoria, articolo NIS2 di riferimento e versione. Opzionalmente include il testo completo.

#### Query Parameters

Parametro | Tipo | Descrizione
--- | --- | ---
`category` | `string` | Filtra per categoria (es. `incident_response`)
`include_content` | `boolean` | Include testo policy (default: false)

## Gestione API Keys

CRUD API Keys. Richiede autenticazione JWT con ruolo `org_admin`.

GET /webhooks/api-keys JWT Lista API Keys dell'organizzazione

Restituisce tutte le API Keys (attive e revocate) dell'organizzazione corrente. Non espone mai il testo completo della chiave.

POST /webhooks/api-keys JWT org_admin
La chiave completa viene restituita SOLO UNA VOLTA nella risposta di creazione. Salvarla immediatamente.

#### Body (JSON)

Campo | Tipo | Obbligatorio | Descrizione
--- | --- | --- | ---
`name` | `string` | * | Nome descrittivo (es. "SIEM Integration")
`scopes` | `array` | * | Array di scope (es. `["read:risks","read:incidents"]`)
`expires_at` | `datetime` |  | Scadenza opzionale (ISO 8601)

#### Risposta 201

    {
      "id": 5,
      "name": "SIEM Integration",
      "key": "nis2_a3f8c2d1e4b7...",
      "key_prefix": "nis2_a3f8c2",
      "scopes": ["read:risks", "read:incidents"],
      "warning": "Salva questa chiave in modo sicuro. Non sara' piu' visibile."
    }

DELETE /webhooks/api-keys/{id} JWT org_admin Revoca (soft delete) API Key

Revoca una API Key impostando `is_active = 0`. La chiave non viene eliminata dal DB per mantenere l'audit trail.

## Webhook Subscriptions

Gestione sottoscrizioni webhook outbound. NIS2 Agile invierà POST HTTP all'URL configurato al verificarsi degli eventi sottoscritti.

GET /webhooks/subscriptions JWT Lista subscriptions + statistiche delivery

Include statistiche di delivery (total, success, failed) per ogni subscription. Non espone il secret HMAC.

POST /webhooks/subscriptions JWT org_admin
Il secret HMAC viene restituito SOLO UNA VOLTA. Usarlo per verificare la firma `X-NIS2-Signature`.

#### Body (JSON)

Campo | Tipo | Obbligatorio | Descrizione
--- | --- | --- | ---
`name` | `string` | * | Nome descrittivo
`url` | `string` | * | URL https:// destinazione POST
`events` | `array` | * | Array eventi (o `["*"]` per wildcard)

#### Risposta 201

    {
      "id": 3,
      "secret": "a3f8c2d1e4b7f9a2c8d3e1f4...",
      "warning": "Salva il secret. Sara' usato per verificare la firma X-NIS2-Signature."
    }

PUT /webhooks/subscriptions/{id} JWT Aggiorna name, events, is_active

Aggiornamento parziale (PATCH semantics). Solo i campi inviati vengono aggiornati: `name`, `events`, `is_active`.

DELETE /webhooks/subscriptions/{id} JWT org_admin Elimina subscription e storico delivery
POST /webhooks/subscriptions/{id}/test JWT Invia evento webhook.test

Invia un ping di test per verificare che l'endpoint remoto sia raggiungibile e risponda correttamente. Controlla il delivery log per il risultato.

GET /webhooks/deliveries JWT Log ultimi 100 delivery

#### Query Parameters

Parametro | Tipo | Descrizione
--- | --- | ---
`subscription_id` | `integer` | Filtra per subscription specifica

## Catalogo Eventi Webhook

NIS2 Agile emette eventi su tutte le operazioni rilevanti per la compliance. Ogni evento ha un payload tipizzato.

Evento | Trigger | Payload
--- | --- | ---
`incident.created` | Nuovo incidente registrato | `incidentPayload(incident, 'created')`
`incident.updated` | Incidente modificato | `incidentPayload(incident, 'updated')`
`incident.significant` | Incidente flaggato Art.23 | `incidentPayload` con deadlines
`incident.deadline_warning` | Scadenza 24h/72h imminente | deadline + ore rimanenti
`risk.high_created` | Rischio HIGH o CRITICAL creato | `riskPayload(risk, 'created')`
`risk.updated` | Rischio aggiornato | `riskPayload(risk, 'updated')`
`compliance.score_changed` | Variazione score >5% | previous_score, new_score, delta, label
`policy.approved` | Policy approvata | `policyPayload(policy)`
`policy.created` | Nuova policy creata | id, title, category, nis2_article
`supplier.risk_flagged` | Fornitore con rischio HIGH/CRITICAL | supplier + risk_level
`assessment.completed` | Gap assessment completato | score, gap_count, top_gaps[]
`whistleblowing.received` | Nuova segnalazione anonima | id, category, priority (no PII)
`normative.update` | Aggiornamento normativo NIS2/ACN | title, source, effective_date
`webhook.test` | Ping manuale di test | message, timestamp
`*` | Wildcard — tutti gli eventi | —

### Struttura envelope payload

    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "event": "incident.created",
      "api_version": "1.0.0",
      "created": 1709900400,
      "created_at": "2026-03-07T10:00:00+01:00",
      "source": "nis2-agile",
      "org_id": 42,
      "data": { "action": "created", "incident": { ... } }
    }

## Verifica Firma Webhook

Ogni delivery include l'header `X-NIS2-Signature` con firma HMAC-SHA256 del body. Verifica sempre la firma prima di processare il payload.

Pattern identico a Stripe webhook verification: `sha256=HMAC_SHA256(body, secret)`

### Headers inviati

    X-NIS2-Signature: sha256=a3f8c2d1e4b7...
    X-NIS2-Event: incident.created
    X-NIS2-Delivery-Id: 147
    X-NIS2-Attempt: 1
    Content-Type: application/json

### Verifica in PHP

    $body = file_get_contents('php://input');
    $secret = 'il-tuo-secret';
    $signature = $_SERVER['HTTP_X_NIS2_SIGNATURE'];
    $expected = 'sha256=' . hash_hmac('sha256', $body, $secret);

    if (!hash_equals($expected, $signature)) {
        http_response_code(401);
        exit('Invalid signature');
    }

### Verifica in Python

    import hmac, hashlib

    secret = b'il-tuo-secret'
    body = request.data  # bytes
    expected = 'sha256=' + hmac.new(secret, body, hashlib.sha256).hexdigest()
    received = request.headers.get('X-NIS2-Signature', '')

    if not hmac.compare_digest(expected, received):
        abort(401)

### Retry Policy

Tentativo | Ritardo | Note
--- | --- | ---
1° | Immediato | Fire-and-forget sincrono
2° | +5 minuti | Processato da cron
3° | +30 minuti | Ultimo tentativo

Dopo 10 fallimenti consecutivi la subscription viene messa in pausa automatica (`failure_count >= 10`).

NIS2 Agile API Reference v1.0.0 — © 2026 CertiSource Srl — nis2.agile.software