Authentication
All API requests (except /scan/demo and /health) require a valid JWT token obtained from your NIS2Engine account.
Include the token in the Authorization header:
Authorization: Bearer YOUR_JWT_TOKENObtaining a Token:
Tokens are obtained by authenticating with Supabase Auth using your account credentials. Enterprise customers receive a long-lived API token. Contact support for setup.
curl -X POST https://api.yourdomain.com/vendors \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain": "vendor.com", "consent_given": true}'Rate Limits
| Endpoint | Limit |
|---|---|
| POST /scan/demo | 2 per minute per IP |
| POST /scans/trigger | 5 per minute per account |
| GET endpoints | 100 per minute per account |
| POST /reports/letter | 10 per minute per account |
When a rate limit is exceeded, the API returns HTTP 429 Too Many Requests with a Retry-After header.
{
"detail": "Rate limit exceeded. Retry after 30 seconds."
}Error Responses
The API uses standard HTTP status codes:
- 200 Success
- 201 Created
- 400 Bad Request — invalid input
- 401 Unauthorized — missing or invalid token
- 403 Forbidden — insufficient permissions
- 404 Not Found
- 422 Validation Error — see details field
- 429 Rate Limited
- 500 Internal Server Error
{
"detail": "Human readable error message",
"code": "ERROR_CODE",
"timestamp": "2026-06-27T10:00:00Z"
}Endpoint Reference
/health
No auth required. Returns platform health status.
{
"status": "healthy",
"version": "1.0.0",
"timestamp": "2026-06-27T10:00:00Z"
}/vendors
Auth required. Returns all vendors for the authenticated account.
{
"vendors": [
{
"id": "uuid",
"domain": "stripe.com",
"vendor_name": "Stripe Inc",
"status": "complete",
"current_score": 100,
"current_grade": "A",
"risk_level": "Low",
"last_scanned_at": "2026-06-27T10:00:00Z",
"created_at": "2026-01-15T08:00:00Z"
}
],
"total": 5
}/vendors
Auth required. Add a new vendor to your monitoring portfolio.
consent_given must be true. By submitting this request, you confirm that you are authorized to conduct security assessments of this domain and that this falls within the scope of your legitimate business operations.
// Request
{
"domain": "vendor.com", // required
"vendor_name": "Vendor Ltd", // optional
"notes": "Payment processor", // optional
"consent_given": true // required, must be true
}
// Response 201
{
"id": "uuid",
"domain": "vendor.com",
"status": "pending",
"created_at": "2026-06-27T10:00:00Z"
}/vendors/{id}
Auth required. Remove a vendor from monitoring.
// Response 200
{
"message": "Vendor deleted successfully"
}/scans/trigger
Auth required. Trigger a manual security scan for a vendor. The scan runs asynchronously.
// Request
{
"vendor_id": "uuid"
}
// Response 200
{
"message": "Scan queued",
"vendor_id": "uuid",
"status": "scanning"
}/scans/history/{vendor_id}
Auth required. Returns scan history for a specific vendor.
{
"scans": [
{
"id": "uuid",
"score": 100,
"grade": "A",
"risk_level": "Low",
"pdf_url": "https://...",
"scanned_at": "2026-06-27T10:00:00Z",
"score_data": {
"deductions": [...],
"bonuses": [...],
"nis2_mapping": {...}
}
}
]
}/dashboard/summary
Auth required. Returns portfolio-level statistics.
{
"total_vendors": 12,
"scanned_this_month": 12,
"critical_risks": 1,
"average_score": 78,
"grade_distribution": {
"A": 6, "B": 3, "C": 2, "D": 1, "F": 0
}
}/reports/letter
Auth required. Generate a remediation letter for a vendor.
// Request
{
"vendor_id": "uuid",
"language": "en" // en, de, fr, es, ar
}
// Response 200
{
"letter": "Full letter text...",
"vendor": "vendor.com",
"language": "en",
"generated_at": "2026-06-27T10:00:00Z"
}/scan/demo
No auth required. Rate limited: 2/minute per IP. Run a demo scan for any domain. Does not store results or require an account.
Note: Internal IPs (localhost, 192.168.x.x, 10.x.x.x, 127.x.x.x) are blocked.
// Request
{
"domain": "stripe.com"
}Webhooks
Enterprise plan only. Configure webhook URLs in your account settings. NIS2Engine sends POST requests to your configured URL when specific events occur.
SCAN_COMPLETED
Fired when a vendor scan finishes.
{
"event": "scan_completed",
"vendor_id": "uuid",
"domain": "vendor.com",
"score": 78,
"grade": "C",
"risk_level": "Medium",
"pdf_url": "https://...",
"timestamp": "2026-06-27T10:00:00Z"
}GRADE_CHANGED
Fired when a vendor's grade changes between scans.
{
"event": "grade_changed",
"vendor_id": "uuid",
"domain": "vendor.com",
"previous_grade": "B",
"new_grade": "D",
"score_change": -18,
"timestamp": "2026-06-27T10:00:00Z"
}CRITICAL_ALERT
Fired when a vendor drops to grade D or F.
{
"event": "critical_alert",
"vendor_id": "uuid",
"domain": "vendor.com",
"grade": "F",
"score": 20,
"critical_findings": [
"Expired TLS certificate",
"TLS 1.0 still supported"
],
"timestamp": "2026-06-27T10:00:00Z"
}Webhook Security
Verify webhook authenticity by checking the X-NIS2-Signature header against your webhook secret using HMAC-SHA256.
import hmac, hashlib
def verify(payload, secret, signature):
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)Core Data Types
VENDOR OBJECT
{
"id": "uuid string",
"domain": "string — the vendor domain",
"vendor_name": "string | null",
"notes": "string | null",
"consent_given": "boolean",
"consent_timestamp": "ISO 8601 datetime",
"status": "pending | scanning | complete | error",
"current_score": "integer 0-100 | null",
"current_grade": "A | B | C | D | F | null",
"risk_level": "Low | Medium | High | Critical | null",
"last_scanned_at": "ISO 8601 datetime | null",
"created_at": "ISO 8601 datetime"
}SCORE DATA OBJECT
{
"score": "integer 0-100",
"grade": "A | B | C | D | F",
"risk_level": "Low | Medium | High | Critical",
"deductions": [
{
"reason": "string",
"penalty": "integer"
}
],
"bonuses": [
{
"reason": "string",
"bonus": "integer"
}
],
"nis2_mapping": {
"article_21_2a": "compliant | partial | non-compliant",
"article_21_2d": "compliant | partial | non-compliant",
"article_21_2e": "compliant | partial | non-compliant",
"article_21_2f": "compliant | partial | non-compliant",
"article_21_2g": "compliant | partial | non-compliant",
"article_21_2j": "compliant | partial | non-compliant"
},
"critical_risks": ["string"],
"positive_findings": ["string"],
"remediation_steps": [
{
"priority": "Critical | High | Medium",
"action": "string",
"deadline_days": "integer"
}
],
"technical_summary": "string",
"executive_summary": "string"
}