{ "info": { "name": "NIS2 Agile — License API", "description": "API per la gestione licenze NIS2 Agile da sistemi esterni (mktg-agile, e-commerce, partner).\n\nAutenticazione: API Key con scope `admin:licenses`\nHeader: `X-API-Key: nis2_xxxx`\n\nPer ottenere una chiave: login su https://nis2.agile.software → licenseExt.html → login → Settings → API Keys → scope: admin:licenses", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_postman_id": "nis2-license-api-v1" }, "variable": [ { "key": "base_url", "value": "https://nis2.agile.software/api", "type": "string" }, { "key": "api_key", "value": "nis2_LA_TUA_CHIAVE_QUI", "type": "string" }, { "key": "jwt", "value": "", "type": "string" }, { "key": "invite_id","value": "1", "type": "string" } ], "item": [ { "name": "0. Auth — Ottieni JWT (alternativa all'API Key)", "item": [ { "name": "POST /auth/login", "event": [ { "listen": "test", "script": { "exec": [ "const d = pm.response.json();", "if (d.success) {", " pm.collectionVariables.set('jwt', d.data.access_token);", " console.log('JWT salvato:', d.data.access_token.substring(0,30) + '...');", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"email\": \"cristiano.benassati@gmail.com\",\n \"password\": \"Silvia1978!@\"\n}" }, "url": { "raw": "{{base_url}}/auth/login", "host": ["{{base_url}}"], "path": ["auth","login"] }, "description": "Ottieni JWT super_admin (valido 2h). Alternativa all'API Key per test manuali.\nIl JWT viene salvato automaticamente nella variabile {{jwt}}." } } ] }, { "name": "1. Crea Licenza", "item": [ { "name": "POST /invites/create — Licenza singola professional 12m", "event": [ { "listen": "test", "script": { "exec": [ "const d = pm.response.json();", "pm.test('Status 201', () => pm.response.to.have.status(201));", "pm.test('Contiene token', () => pm.expect(d.data.invites[0].token).to.match(/^inv_/));", "if (d.success && d.data.invites.length) {", " pm.collectionVariables.set('invite_id', d.data.invites[0].id);", " console.log('TOKEN (salva subito!):', d.data.invites[0].token);", " console.log('URL onboarding:', d.data.invites[0].invite_url);", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-API-Key", "value": "{{api_key}}" } ], "body": { "mode": "raw", "raw": "{\n \"plan\": \"professional\",\n \"duration_months\": 12,\n \"invite_expires_days\": 30,\n \"max_uses\": 1,\n \"max_users_per_org\": 10,\n \"label\": \"NIS2 Professional — Ordine MKT-2026-001\",\n \"channel\": \"ecommerce\",\n \"issued_to\": \"cliente@azienda.it\",\n \"reseller_name\": \"mktg-agile S.r.l.\",\n \"price_eur\": 990.00,\n \"notes\": \"Campagna Q1 2026\"\n}" }, "url": { "raw": "{{base_url}}/invites/create", "host": ["{{base_url}}"], "path": ["invites","create"] }, "description": "Crea una licenza singola Professional 12 mesi.\n\nRESPONSE (201):\n- `invites[].token` — inv_xxx... → SALVARE SUBITO, non recuperabile\n- `invites[].invite_url` — URL per attivare da browser\n- `invites[].expires_at` — scadenza invito (entro cui attivare)\n- `warning` — promemoria sicurezza token" } }, { "name": "POST /invites/create — Batch 5 licenze Essentials", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-API-Key", "value": "{{api_key}}" } ], "body": { "mode": "raw", "raw": "{\n \"plan\": \"essentials\",\n \"duration_months\": 6,\n \"invite_expires_days\": 60,\n \"max_uses\": 1,\n \"max_users_per_org\": 3,\n \"quantity\": 5,\n \"label\": \"Essentials 6m — Bundle Reseller Q1\",\n \"channel\": \"reseller\",\n \"reseller_name\": \"Partner XYZ\",\n \"price_eur\": 490.00,\n \"notes\": \"Batch 5 licenze per partner XYZ\"\n}" }, "url": { "raw": "{{base_url}}/invites/create", "host": ["{{base_url}}"], "path": ["invites","create"] }, "description": "Genera 5 token in un'unica chiamata (quantity=5).\nRisposta contiene array `invites` con 5 elementi, ciascuno con token unico." } }, { "name": "POST /invites/create — Enterprise con restrizione P.IVA", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-API-Key", "value": "{{api_key}}" } ], "body": { "mode": "raw", "raw": "{\n \"plan\": \"enterprise\",\n \"duration_months\": 24,\n \"invite_expires_days\": 14,\n \"max_uses\": 1,\n \"label\": \"Enterprise 24m — Cliente Acme S.r.l.\",\n \"channel\": \"direct\",\n \"issued_to\": \"ciso@acme.it\",\n \"restrict_vat\": \"02345678901\",\n \"restrict_email\": \"ciso@acme.it\",\n \"price_eur\": 4800.00,\n \"notes\": \"Contratto enterprise diretto — approvato da DG\"\n}" }, "url": { "raw": "{{base_url}}/invites/create", "host": ["{{base_url}}"], "path": ["invites","create"] }, "description": "Licenza Enterprise riservata a una specifica P.IVA e email admin.\nSe usata da P.IVA diversa → errore 403 INVITE_VAT_MISMATCH." } } ] }, { "name": "2. Lista Licenze", "item": [ { "name": "GET /invites/list — tutte", "request": { "method": "GET", "header": [{ "key": "X-API-Key", "value": "{{api_key}}" }], "url": { "raw": "{{base_url}}/invites/list?limit=50", "host": ["{{base_url}}"], "path": ["invites","list"], "query": [ { "key": "limit", "value": "50" }, { "key": "offset", "value": "0", "disabled": true } ] }, "description": "Lista tutte le licenze. Filtrabile per status e channel.\nCampi risposta: id, token_prefix, plan, status, used_count, max_uses, max_users_per_org, price_eur, reseller_name, expires_at, channel, label, issued_to, used_by_org_id." } }, { "name": "GET /invites/list — solo pending (attive)", "request": { "method": "GET", "header": [{ "key": "X-API-Key", "value": "{{api_key}}" }], "url": { "raw": "{{base_url}}/invites/list?status=pending&limit=100", "host": ["{{base_url}}"], "path": ["invites","list"], "query": [ { "key": "status", "value": "pending" }, { "key": "limit", "value": "100" } ] } } }, { "name": "GET /invites/list — per canale ecommerce", "request": { "method": "GET", "header": [{ "key": "X-API-Key", "value": "{{api_key}}" }], "url": { "raw": "{{base_url}}/invites/list?channel=ecommerce&status=pending", "host": ["{{base_url}}"], "path": ["invites","list"], "query": [ { "key": "channel", "value": "ecommerce" }, { "key": "status", "value": "pending" } ] } } }, { "name": "GET /invites/{id} — dettaglio singolo", "request": { "method": "GET", "header": [{ "key": "X-API-Key", "value": "{{api_key}}" }], "url": { "raw": "{{base_url}}/invites/{{invite_id}}", "host": ["{{base_url}}"], "path": ["invites","{{invite_id}}"] }, "description": "Dettaglio completo. Se usata, include used_by_org con name/sector/nis2_entity_type." } } ] }, { "name": "3. Revoca / Rigenera", "item": [ { "name": "DELETE /invites/{id} — revoca", "request": { "method": "DELETE", "header": [{ "key": "X-API-Key", "value": "{{api_key}}" }], "url": { "raw": "{{base_url}}/invites/{{invite_id}}", "host": ["{{base_url}}"], "path": ["invites","{{invite_id}}"] }, "description": "Revoca la licenza. Operazione non reversibile.\nLa licenza passa a status=revoked. Eventuali aziende già provisionate rimangono attive (il provisioning è già avvenuto).\nNon si può revocare una licenza già usata (status=used)." } }, { "name": "POST /invites/{id}/regenerate — nuovo token", "request": { "method": "POST", "header": [{ "key": "X-API-Key", "value": "{{api_key}}" }], "url": { "raw": "{{base_url}}/invites/{{invite_id}}/regenerate", "host": ["{{base_url}}"], "path": ["invites","{{invite_id}}","regenerate"] }, "description": "Genera un nuovo token invalidando il vecchio. Utile se il token è stato inviato per errore.\nRisposta: { id, token, token_prefix, warning }. Il nuovo token è visibile una sola volta." } } ] }, { "name": "4. Validazione pubblica (no auth)", "item": [ { "name": "GET /invites/validate?token= — anteprima invito", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/invites/validate?token=inv_INSERISCI_TOKEN_QUI", "host": ["{{base_url}}"], "path": ["invites","validate"], "query": [{ "key": "token", "value": "inv_INSERISCI_TOKEN_QUI" }] }, "description": "Endpoint pubblico — nessuna auth richiesta.\nUsato da lg231 prima del provisioning e dalla pagina onboarding per mostrare l'anteprima piano.\nRisposta: { valid, plan, duration_months, expires_at, remaining_uses, max_users_per_org, plan_features[] }" } } ] }, { "name": "5. Provisioning (per lg231 / e-commerce)", "item": [ { "name": "POST /services/provision — attivazione con invite_token", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"invite_token\": \"inv_INSERISCI_TOKEN_RICEVUTO\",\n \"company\": {\n \"ragione_sociale\": \"Acme S.r.l.\",\n \"partita_iva\": \"02345678901\",\n \"ateco_code\": \"62.01.00\",\n \"sector\": \"ict\"\n },\n \"admin\": {\n \"email\": \"ciso@acme.it\",\n \"first_name\": \"Marco\",\n \"last_name\": \"Rossi\"\n },\n \"caller\": {\n \"system\": \"mktg-agile\",\n \"callback_url\": \"https://mktg.agile.software/api/webhooks/nis2-provisioned\"\n }\n}" }, "url": { "raw": "{{base_url}}/services/provision", "host": ["{{base_url}}"], "path": ["services","provision"] }, "description": "Attiva automaticamente una licenza usando il token ricevuto.\nNON richiede X-API-Key — l'invite_token è l'auth.\nRisposta: org_id, api_key, access_token (JWT 2h), temp_password, license_expires_at, dashboard_url." } } ] } ] }