Updated init_db.py to use config_id field after database migration, fixing container startup error on new systems. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
71 KiB
SneakyScanner Web API Reference
Version: 6.0 (Database-Based Configuration)
Base URL: http://localhost:5000
Authentication: Session-based (Flask-Login)
Table of Contents
- Authentication
- Sites API
- Configs API
- Scans API
- Schedules API
- Stats API
- Settings API
- Alerts API
- Webhooks API
- Error Handling
- Status Codes
- Request/Response Examples
Authentication
SneakyScanner uses session-based authentication with Flask-Login. All API endpoints (except login, setup, and health checks) require authentication.
Setup
Initial password setup for first-time use.
Endpoint: GET|POST /auth/setup
Authentication: Not required (only accessible when no password is set)
GET Request: Returns the setup page template.
POST Request Body:
password: "your-password-here"
confirm_password: "your-password-here"
Success Response: Redirects to login page (302)
Error Responses:
- Password must be at least 8 characters
- Passwords must match
- Password is required
Usage Example:
# Setup initial password
curl -X POST http://localhost:5000/auth/setup \
-F "password=yourpassword" \
-F "confirm_password=yourpassword"
Login
Authenticate and create a session.
Endpoint: POST /auth/login
Authentication: Not required
Request Body:
password: "your-password-here"
remember: false (optional)
Success Response: Redirects to dashboard (302)
Error Response: Returns login page with error message
Usage Example:
# Login and save session cookie
curl -X POST http://localhost:5000/auth/login \
-F "password=yourpassword" \
-c cookies.txt
# Use session cookie for subsequent requests
curl -X GET http://localhost:5000/api/scans \
-b cookies.txt
Logout
Destroy the current session.
Endpoint: GET /auth/logout
Success Response: Redirects to login page (302)
Sites API
Manage reusable site definitions, including CIDR ranges and IP-level overrides. Sites are the building blocks for scan configurations.
List Sites
Retrieve a paginated list of all sites.
Endpoint: GET /api/sites
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number (1-indexed) |
per_page |
integer | No | 20 | Items per page (1-100) |
all |
string | No | - | Set to "true" to return all sites without pagination |
Success Response (200 OK):
{
"sites": [
{
"id": 1,
"name": "Production DC",
"description": "Production datacenter servers",
"ip_count": 25,
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T10:30:00Z"
}
],
"total": 5,
"page": 1,
"per_page": 20,
"total_pages": 1,
"has_prev": false,
"has_next": false
}
Usage Example:
# List first page
curl -X GET http://localhost:5000/api/sites \
-b cookies.txt
# Get all sites (for dropdowns)
curl -X GET "http://localhost:5000/api/sites?all=true" \
-b cookies.txt
Get Site
Retrieve details for a specific site including all IPs.
Endpoint: GET /api/sites/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Site ID |
Success Response (200 OK):
{
"id": 1,
"name": "Production DC",
"description": "Production datacenter servers",
"ip_count": 25,
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T10:30:00Z"
}
Usage Example:
curl -X GET http://localhost:5000/api/sites/1 \
-b cookies.txt
Create Site
Create a new site.
Endpoint: POST /api/sites
Authentication: Required
Request Body:
{
"name": "Production DC",
"description": "Production datacenter servers"
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Site name (must be unique) |
description |
string | No | Site description |
Success Response (201 Created):
{
"id": 1,
"name": "Production DC",
"description": "Production datacenter servers",
"ip_count": 0,
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T10:30:00Z"
}
Usage Example:
curl -X POST http://localhost:5000/api/sites \
-H "Content-Type: application/json" \
-d '{"name":"Production DC","description":"Production servers"}' \
-b cookies.txt
Update Site
Update site metadata.
Endpoint: PUT /api/sites/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Site ID |
Request Body:
{
"name": "Updated Name",
"description": "Updated description"
}
Success Response (200 OK):
{
"id": 1,
"name": "Updated Name",
"description": "Updated description",
"ip_count": 25,
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T11:00:00Z"
}
Delete Site
Delete a site. Fails if site is used in any scan.
Endpoint: DELETE /api/sites/{id}
Authentication: Required
Success Response (200 OK):
{
"message": "Site 1 deleted successfully"
}
Bulk Add IPs
Add multiple IPs to a site from CIDR or list.
Endpoint: POST /api/sites/{id}/ips/bulk
Authentication: Required
Request Body (CIDR):
{
"source_type": "cidr",
"cidr": "10.0.0.0/24",
"expected_ping": true,
"expected_tcp_ports": [22, 80, 443],
"expected_udp_ports": [53]
}
Request Body (List):
{
"source_type": "list",
"ips": ["10.0.0.1", "10.0.0.2", "10.0.0.3"],
"expected_ping": true,
"expected_tcp_ports": [22, 80, 443],
"expected_udp_ports": []
}
Success Response (201 Created):
{
"ip_count": 254,
"errors": []
}
List IPs in Site
List IPs in a site with pagination.
Endpoint: GET /api/sites/{id}/ips
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number |
per_page |
integer | No | 50 | Items per page (max: 200) |
Success Response (200 OK):
{
"ips": [
{
"id": 1,
"ip_address": "10.0.0.1",
"expected_ping": true,
"expected_tcp_ports": [22, 80, 443],
"expected_udp_ports": []
}
],
"total": 254,
"page": 1,
"per_page": 50,
"total_pages": 6,
"has_prev": false,
"has_next": true
}
Add Standalone IP
Add a single IP to a site.
Endpoint: POST /api/sites/{id}/ips
Authentication: Required
Request Body:
{
"ip_address": "10.0.0.100",
"expected_ping": true,
"expected_tcp_ports": [22, 443],
"expected_udp_ports": []
}
Success Response (201 Created):
{
"id": 100,
"ip_address": "10.0.0.100",
"expected_ping": true,
"expected_tcp_ports": [22, 443],
"expected_udp_ports": []
}
Update IP Settings
Update settings for an individual IP.
Endpoint: PUT /api/sites/{site_id}/ips/{ip_id}
Authentication: Required
Request Body:
{
"expected_ping": false,
"expected_tcp_ports": [443, 8080],
"expected_udp_ports": [53]
}
Remove IP
Remove an IP from a site.
Endpoint: DELETE /api/sites/{site_id}/ips/{ip_id}
Authentication: Required
Success Response (200 OK):
{
"message": "IP 100 removed successfully"
}
Get Site Usage
Get list of scans that use this site.
Endpoint: GET /api/sites/{id}/usage
Authentication: Required
Success Response (200 OK):
{
"site_id": 1,
"site_name": "Production DC",
"scans": [
{
"id": 42,
"title": "Production Scan",
"timestamp": "2025-11-19T10:30:00Z"
}
],
"count": 1
}
Configs API
Manage scan configurations stored in the database. Configs reference one or more sites to define what to scan.
List Configs
Retrieve all scan configurations.
Endpoint: GET /api/configs
Authentication: Required
Success Response (200 OK):
{
"configs": [
{
"id": 1,
"title": "Production Scan",
"description": "Weekly production scan",
"site_count": 3,
"sites": [
{"id": 1, "name": "Production DC"},
{"id": 2, "name": "DMZ"}
],
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T10:30:00Z"
}
]
}
Get Config
Retrieve a specific configuration by ID.
Endpoint: GET /api/configs/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Config ID |
Success Response (200 OK):
{
"id": 1,
"title": "Production Scan",
"description": "Weekly production scan",
"site_count": 3,
"sites": [
{
"id": 1,
"name": "Production DC",
"description": "Production servers",
"ip_count": 25
}
],
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T10:30:00Z"
}
Create Config
Create a new scan configuration.
Endpoint: POST /api/configs
Authentication: Required
Request Body:
{
"title": "Production Scan",
"description": "Weekly production scan",
"site_ids": [1, 2, 3]
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Config title |
description |
string | No | Config description |
site_ids |
array | Yes | Array of site IDs to include |
Success Response (201 Created):
{
"success": true,
"config": {
"id": 1,
"title": "Production Scan",
"description": "Weekly production scan",
"site_count": 3,
"sites": [...],
"created_at": "2025-11-19T10:30:00Z",
"updated_at": "2025-11-19T10:30:00Z"
}
}
Usage Example:
curl -X POST http://localhost:5000/api/configs \
-H "Content-Type: application/json" \
-d '{"title":"My Scan","site_ids":[1,2]}' \
-b cookies.txt
Update Config
Update an existing configuration.
Endpoint: PUT /api/configs/{id}
Authentication: Required
Request Body:
{
"title": "Updated Title",
"description": "Updated description",
"site_ids": [1, 2, 3, 4]
}
Note: All fields are optional. Only provided fields will be updated.
Success Response (200 OK):
{
"success": true,
"config": {...}
}
Delete Config
Delete a configuration.
Endpoint: DELETE /api/configs/{id}
Authentication: Required
Success Response (200 OK):
{
"success": true,
"message": "Config deleted successfully"
}
Add Site to Config
Add a site to an existing config.
Endpoint: POST /api/configs/{config_id}/sites
Authentication: Required
Request Body:
{
"site_id": 5
}
Success Response (200 OK):
{
"success": true,
"config": {...}
}
Remove Site from Config
Remove a site from a config.
Endpoint: DELETE /api/configs/{config_id}/sites/{site_id}
Authentication: Required
Success Response (200 OK):
{
"success": true,
"config": {...}
}
Scans API
Manage network scans: trigger, list, view, and delete.
Trigger Scan
Start a new background scan.
Endpoint: POST /api/scans
Authentication: Required
Request Body:
{
"config_id": 1
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
config_id |
integer | Yes | Database config ID |
Success Response (201 Created):
{
"scan_id": 42,
"status": "running",
"message": "Scan queued successfully"
}
Error Responses:
400 Bad Request - Invalid or missing config_id:
{
"error": "Invalid request",
"message": "config_id is required"
}
400 Bad Request - Config not found:
{
"error": "Invalid request",
"message": "Config with ID 99 not found"
}
Usage Example:
curl -X POST http://localhost:5000/api/scans \
-H "Content-Type: application/json" \
-d '{"config_id":1}' \
-b cookies.txt
List Scans
Retrieve a paginated list of scans with optional status filtering.
Endpoint: GET /api/scans
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number (1-indexed) |
per_page |
integer | No | 20 | Items per page (1-100) |
status |
string | No | - | Filter by status: running, completed, failed |
Success Response (200 OK):
{
"scans": [
{
"id": 42,
"timestamp": "2025-11-14T10:30:00Z",
"duration": 125.5,
"status": "completed",
"title": "Production Network Scan",
"config_file": "/app/configs/production.yaml",
"triggered_by": "manual",
"started_at": "2025-11-14T10:30:00Z",
"completed_at": "2025-11-14T10:32:05Z"
},
{
"id": 41,
"timestamp": "2025-11-13T15:00:00Z",
"duration": 98.2,
"status": "completed",
"title": "Development Network Scan",
"config_file": "/app/configs/dev.yaml",
"triggered_by": "scheduled",
"started_at": "2025-11-13T15:00:00Z",
"completed_at": "2025-11-13T15:01:38Z"
}
],
"total": 42,
"page": 1,
"per_page": 20,
"total_pages": 3,
"has_prev": false,
"has_next": true
}
Error Responses:
400 Bad Request - Invalid parameters:
{
"error": "Invalid pagination parameters",
"message": "Page and per_page must be positive integers"
}
Usage Examples:
# List first page (default 20 items)
curl -X GET http://localhost:5000/api/scans \
-b cookies.txt
# List page 2 with 50 items per page
curl -X GET "http://localhost:5000/api/scans?page=2&per_page=50" \
-b cookies.txt
# List only running scans
curl -X GET "http://localhost:5000/api/scans?status=running" \
-b cookies.txt
Get Scan Details
Retrieve complete details for a specific scan, including all sites, IPs, ports, services, certificates, and TLS versions.
Endpoint: GET /api/scans/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Scan ID |
Success Response (200 OK):
{
"id": 42,
"timestamp": "2025-11-14T10:30:00Z",
"duration": 125.5,
"status": "completed",
"title": "Production Network Scan",
"config_file": "/app/configs/production.yaml",
"json_path": "/app/output/scan_report_20251114_103000.json",
"html_path": "/app/output/scan_report_20251114_103000.html",
"zip_path": "/app/output/scan_report_20251114_103000.zip",
"screenshot_dir": "/app/output/scan_report_20251114_103000_screenshots",
"triggered_by": "manual",
"started_at": "2025-11-14T10:30:00Z",
"completed_at": "2025-11-14T10:32:05Z",
"error_message": null,
"sites": [
{
"id": 101,
"site_name": "Production Web Servers",
"ips": [
{
"id": 201,
"ip_address": "192.168.1.10",
"ping_expected": true,
"ping_actual": true,
"ports": [
{
"id": 301,
"port": 443,
"protocol": "tcp",
"expected": true,
"state": "open",
"services": [
{
"id": 401,
"service_name": "https",
"product": "nginx",
"version": "1.24.0",
"extrainfo": null,
"ostype": "Linux",
"http_protocol": "https",
"screenshot_path": "scan_report_20251114_103000_screenshots/192_168_1_10_443.png",
"certificates": [
{
"id": 501,
"subject": "CN=example.com",
"issuer": "CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US",
"serial_number": "123456789012345678901234567890",
"not_valid_before": "2025-01-01T00:00:00+00:00",
"not_valid_after": "2025-04-01T23:59:59+00:00",
"days_until_expiry": 89,
"sans": "[\"example.com\", \"www.example.com\"]",
"is_self_signed": false,
"tls_versions": [
{
"id": 601,
"tls_version": "TLS 1.2",
"supported": true,
"cipher_suites": "[\"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\"]"
},
{
"id": 602,
"tls_version": "TLS 1.3",
"supported": true,
"cipher_suites": "[\"TLS_AES_256_GCM_SHA384\", \"TLS_AES_128_GCM_SHA256\"]"
}
]
}
]
}
]
}
]
}
]
}
]
}
Error Responses:
404 Not Found - Scan doesn't exist:
{
"error": "Scan not found",
"message": "Scan with ID 42 does not exist"
}
Usage Example:
curl -X GET http://localhost:5000/api/scans/42 \
-b cookies.txt
Get Scan Status
Poll the current status of a running scan. Use this endpoint to track scan progress.
Endpoint: GET /api/scans/{id}/status
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Scan ID |
Success Response (200 OK):
Running scan:
{
"scan_id": 42,
"status": "running",
"started_at": "2025-11-14T10:30:00Z",
"completed_at": null,
"error_message": null
}
Completed scan:
{
"scan_id": 42,
"status": "completed",
"started_at": "2025-11-14T10:30:00Z",
"completed_at": "2025-11-14T10:32:05Z",
"error_message": null
}
Failed scan:
{
"scan_id": 42,
"status": "failed",
"started_at": "2025-11-14T10:30:00Z",
"completed_at": "2025-11-14T10:30:15Z",
"error_message": "Config file not found: /app/configs/missing.yaml"
}
Error Responses:
404 Not Found - Scan doesn't exist:
{
"error": "Scan not found",
"message": "Scan with ID 42 does not exist"
}
Usage Example:
# Poll status every 5 seconds
while true; do
curl -X GET http://localhost:5000/api/scans/42/status -b cookies.txt
sleep 5
done
Delete Scan
Delete a scan and all associated files (JSON, HTML, ZIP, screenshots).
Endpoint: DELETE /api/scans/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Scan ID |
Success Response (200 OK):
{
"message": "Scan 42 deleted successfully"
}
Error Responses:
404 Not Found - Scan doesn't exist:
{
"error": "Scan not found",
"message": "Scan with ID 42 does not exist"
}
Usage Example:
curl -X DELETE http://localhost:5000/api/scans/42 \
-b cookies.txt
Compare Scans
Compare two scans to identify differences in ports, services, and certificates.
Endpoint: GET /api/scans/{scan_id1}/compare/{scan_id2}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
scan_id1 |
integer | Yes | First (older) scan ID |
scan_id2 |
integer | Yes | Second (newer) scan ID |
Success Response (200 OK):
{
"scan1": {
"id": 41,
"timestamp": "2025-11-13T15:00:00Z",
"title": "Development Network Scan"
},
"scan2": {
"id": 42,
"timestamp": "2025-11-14T10:30:00Z",
"title": "Production Network Scan"
},
"ports": {
"added": [{"ip": "192.168.1.10", "port": 8080, "protocol": "tcp"}],
"removed": [{"ip": "192.168.1.11", "port": 22, "protocol": "tcp"}],
"unchanged": [{"ip": "192.168.1.10", "port": 443, "protocol": "tcp"}]
},
"services": {
"added": [{"ip": "192.168.1.10", "port": 8080, "service_name": "http-proxy"}],
"removed": [{"ip": "192.168.1.11", "port": 22, "service_name": "ssh"}],
"changed": [
{
"ip": "192.168.1.10",
"port": 443,
"old_version": "1.23.0",
"new_version": "1.24.0"
}
]
},
"certificates": {
"added": [],
"removed": [],
"changed": [
{
"ip": "192.168.1.10",
"port": 443,
"old_expiry": "2025-04-01",
"new_expiry": "2025-07-01"
}
]
},
"drift_score": 0.15
}
Error Responses:
404 Not Found - One or both scans don't exist:
{
"error": "Not found",
"message": "One or both scans not found"
}
Usage Example:
curl -X GET http://localhost:5000/api/scans/41/compare/42 \
-b cookies.txt
Health Check
Check API health status.
Endpoint: GET /api/scans/health
Authentication: Not required
Success Response (200 OK):
{
"status": "healthy",
"api": "scans",
"version": "1.0.0-phase1"
}
Schedules API
Manage scheduled scans including creation, updates, and manual triggering.
List Schedules
Retrieve a list of all schedules with pagination and filtering.
Endpoint: GET /api/schedules
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number (1-indexed) |
per_page |
integer | No | 20 | Items per page |
enabled |
boolean | No | - | Filter by enabled status (true/false) |
Success Response (200 OK):
{
"schedules": [
{
"id": 1,
"name": "Daily Production Scan",
"config_file": "/app/configs/prod-scan.yaml",
"cron_expression": "0 2 * * *",
"enabled": true,
"created_at": "2025-11-01T10:00:00Z",
"updated_at": "2025-11-01T10:00:00Z",
"last_run": "2025-11-15T02:00:00Z",
"next_run": "2025-11-16T02:00:00Z"
}
],
"total": 5,
"page": 1,
"per_page": 20,
"total_pages": 1
}
Usage Example:
# List all schedules
curl -X GET http://localhost:5000/api/schedules \
-b cookies.txt
# List only enabled schedules
curl -X GET "http://localhost:5000/api/schedules?enabled=true" \
-b cookies.txt
Get Schedule
Retrieve details for a specific schedule including execution history.
Endpoint: GET /api/schedules/{schedule_id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
schedule_id |
integer | Yes | Schedule ID |
Success Response (200 OK):
{
"id": 1,
"name": "Daily Production Scan",
"config_file": "/app/configs/prod-scan.yaml",
"cron_expression": "0 2 * * *",
"enabled": true,
"created_at": "2025-11-01T10:00:00Z",
"updated_at": "2025-11-01T10:00:00Z",
"last_run": "2025-11-15T02:00:00Z",
"next_run": "2025-11-16T02:00:00Z",
"execution_history": [
{
"scan_id": 42,
"timestamp": "2025-11-15T02:00:00Z",
"status": "completed"
}
]
}
Error Responses:
404 Not Found - Schedule doesn't exist:
{
"error": "Schedule not found"
}
Usage Example:
curl -X GET http://localhost:5000/api/schedules/1 \
-b cookies.txt
Create Schedule
Create a new scheduled scan.
Endpoint: POST /api/schedules
Authentication: Required
Request Body:
{
"name": "Daily Production Scan",
"config_file": "/app/configs/prod-scan.yaml",
"cron_expression": "0 2 * * *",
"enabled": true
}
Success Response (201 Created):
{
"schedule_id": 1,
"message": "Schedule created successfully",
"schedule": {
"id": 1,
"name": "Daily Production Scan",
"config_file": "/app/configs/prod-scan.yaml",
"cron_expression": "0 2 * * *",
"enabled": true,
"created_at": "2025-11-01T10:00:00Z"
}
}
Error Responses:
400 Bad Request - Missing required fields or invalid cron expression:
{
"error": "Missing required fields: name, config_file"
}
Usage Example:
curl -X POST http://localhost:5000/api/schedules \
-H "Content-Type: application/json" \
-d '{"name":"Daily Scan","config_file":"/app/configs/prod.yaml","cron_expression":"0 2 * * *"}' \
-b cookies.txt
Update Schedule
Update an existing schedule.
Endpoint: PUT /api/schedules/{schedule_id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
schedule_id |
integer | Yes | Schedule ID to update |
Request Body:
{
"name": "Updated Schedule Name",
"cron_expression": "0 3 * * *",
"enabled": false
}
Success Response (200 OK):
{
"message": "Schedule updated successfully",
"schedule": {
"id": 1,
"name": "Updated Schedule Name",
"config_file": "/app/configs/prod-scan.yaml",
"cron_expression": "0 3 * * *",
"enabled": false,
"updated_at": "2025-11-15T10:00:00Z"
}
}
Error Responses:
404 Not Found - Schedule doesn't exist:
{
"error": "Schedule not found"
}
400 Bad Request - Invalid cron expression or validation error:
{
"error": "Invalid cron expression"
}
Usage Example:
curl -X PUT http://localhost:5000/api/schedules/1 \
-H "Content-Type: application/json" \
-d '{"enabled":false}' \
-b cookies.txt
Delete Schedule
Delete a schedule. Associated scans are not deleted.
Endpoint: DELETE /api/schedules/{schedule_id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
schedule_id |
integer | Yes | Schedule ID to delete |
Success Response (200 OK):
{
"message": "Schedule deleted successfully",
"schedule_id": 1
}
Error Responses:
404 Not Found - Schedule doesn't exist:
{
"error": "Schedule not found"
}
Usage Example:
curl -X DELETE http://localhost:5000/api/schedules/1 \
-b cookies.txt
Trigger Schedule
Manually trigger a scheduled scan immediately.
Endpoint: POST /api/schedules/{schedule_id}/trigger
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
schedule_id |
integer | Yes | Schedule ID to trigger |
Success Response (201 Created):
{
"message": "Scan triggered successfully",
"schedule_id": 1,
"scan_id": 43
}
Error Responses:
404 Not Found - Schedule doesn't exist:
{
"error": "Schedule not found"
}
Usage Example:
curl -X POST http://localhost:5000/api/schedules/1/trigger \
-b cookies.txt
Health Check
Check API health status.
Endpoint: GET /api/schedules/health
Authentication: Not required
Success Response (200 OK):
{
"status": "healthy",
"api": "schedules",
"version": "1.0.0-phase1"
}
Stats API
Retrieve dashboard statistics, trends, and analytics data.
Scan Trend
Get scan activity trend data for charts.
Endpoint: GET /api/stats/scan-trend
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
days |
integer | No | 30 | Number of days to include (1-365) |
Success Response (200 OK):
{
"labels": ["2025-01-01", "2025-01-02", "2025-01-03"],
"values": [5, 3, 7],
"start_date": "2025-01-01",
"end_date": "2025-01-30",
"total_scans": 150
}
Error Responses:
400 Bad Request - Invalid days parameter:
{
"error": "days parameter must be at least 1"
}
Usage Example:
# Get last 30 days trend
curl -X GET http://localhost:5000/api/stats/scan-trend \
-b cookies.txt
# Get last 90 days trend
curl -X GET "http://localhost:5000/api/stats/scan-trend?days=90" \
-b cookies.txt
Summary Statistics
Get dashboard summary statistics.
Endpoint: GET /api/stats/summary
Authentication: Required
Success Response (200 OK):
{
"total_scans": 150,
"completed_scans": 140,
"failed_scans": 5,
"running_scans": 5,
"scans_today": 3,
"scans_this_week": 15
}
Usage Example:
curl -X GET http://localhost:5000/api/stats/summary \
-b cookies.txt
Scan History
Get historical trend data for scans with the same configuration.
Endpoint: GET /api/stats/scan-history/{scan_id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
scan_id |
integer | Yes | Reference scan ID |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
integer | No | 10 | Max historical scans (1-50) |
Success Response (200 OK):
{
"scans": [
{
"id": 40,
"timestamp": "2025-11-10T12:00:00",
"title": "Production Scan",
"port_count": 25,
"ip_count": 5
},
{
"id": 42,
"timestamp": "2025-11-15T12:00:00",
"title": "Production Scan",
"port_count": 26,
"ip_count": 5
}
],
"labels": ["2025-11-10 12:00", "2025-11-15 12:00"],
"port_counts": [25, 26],
"config_file": "/app/configs/prod-scan.yaml"
}
Error Responses:
404 Not Found - Scan doesn't exist:
{
"error": "Scan not found"
}
Usage Example:
curl -X GET http://localhost:5000/api/stats/scan-history/42 \
-b cookies.txt
Settings API
Manage application settings including SMTP configuration, encryption keys, and preferences.
Get All Settings
Retrieve all application settings. Sensitive values (passwords, keys) are masked.
Endpoint: GET /api/settings
Authentication: Required
Success Response (200 OK):
{
"status": "success",
"settings": {
"smtp_server": "smtp.gmail.com",
"smtp_port": 587,
"smtp_username": "alerts@example.com",
"smtp_password": "***ENCRYPTED***",
"smtp_from_email": "alerts@example.com",
"smtp_to_emails": "[\"admin@example.com\"]",
"retention_days": 90,
"app_password": "***ENCRYPTED***"
}
}
Usage Example:
curl -X GET http://localhost:5000/api/settings \
-b cookies.txt
Update Multiple Settings
Update multiple settings at once.
Endpoint: PUT /api/settings
Authentication: Required
Request Body:
{
"settings": {
"smtp_server": "smtp.example.com",
"smtp_port": 587,
"retention_days": 90
}
}
Success Response (200 OK):
{
"status": "success",
"message": "Updated 3 settings"
}
Error Responses:
400 Bad Request - No settings provided:
{
"status": "error",
"message": "No settings provided"
}
Usage Example:
curl -X PUT http://localhost:5000/api/settings \
-H "Content-Type: application/json" \
-d '{"settings":{"smtp_server":"smtp.example.com","smtp_port":587}}' \
-b cookies.txt
Get Single Setting
Retrieve a specific setting by key.
Endpoint: GET /api/settings/{key}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | Setting key |
Success Response (200 OK):
{
"status": "success",
"key": "smtp_server",
"value": "smtp.gmail.com"
}
Error Responses:
404 Not Found - Setting doesn't exist:
{
"status": "error",
"message": "Setting \"invalid_key\" not found"
}
Usage Example:
curl -X GET http://localhost:5000/api/settings/smtp_server \
-b cookies.txt
Update Single Setting
Update a specific setting value.
Endpoint: PUT /api/settings/{key}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | Setting key |
Request Body:
{
"value": "smtp.example.com"
}
Success Response (200 OK):
{
"status": "success",
"message": "Setting \"smtp_server\" updated"
}
Error Responses:
400 Bad Request - Missing value:
{
"status": "error",
"message": "No value provided"
}
Usage Example:
curl -X PUT http://localhost:5000/api/settings/smtp_server \
-H "Content-Type: application/json" \
-d '{"value":"smtp.example.com"}' \
-b cookies.txt
Delete Setting
Delete a specific setting.
Endpoint: DELETE /api/settings/{key}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | Setting key to delete |
Success Response (200 OK):
{
"status": "success",
"message": "Setting \"custom_key\" deleted"
}
Error Responses:
404 Not Found - Setting doesn't exist:
{
"status": "error",
"message": "Setting \"invalid_key\" not found"
}
Usage Example:
curl -X DELETE http://localhost:5000/api/settings/custom_key \
-b cookies.txt
Set Application Password
Set or update the application password.
Endpoint: POST /api/settings/password
Authentication: Required
Request Body:
{
"password": "newpassword123"
}
Success Response (200 OK):
{
"status": "success",
"message": "Password updated successfully"
}
Error Responses:
400 Bad Request - Missing or invalid password:
{
"status": "error",
"message": "Password must be at least 8 characters"
}
Usage Example:
curl -X POST http://localhost:5000/api/settings/password \
-H "Content-Type: application/json" \
-d '{"password":"newpassword123"}' \
-b cookies.txt
Test Email Configuration
Test email settings by sending a test email.
Endpoint: POST /api/settings/test-email
Authentication: Required
Success Response (501 Not Implemented):
{
"status": "not_implemented",
"message": "Email testing endpoint - to be implemented in Phase 4"
}
Note: This endpoint returns 501 Not Implemented. Full email testing functionality will be added in a future phase.
Usage Example:
curl -X POST http://localhost:5000/api/settings/test-email \
-b cookies.txt
Health Check
Check API health status.
Endpoint: GET /api/settings/health
Authentication: Not required
Success Response (200 OK):
{
"status": "healthy",
"api": "settings",
"version": "1.0.0-phase1"
}
Usage Example:
curl -X GET http://localhost:5000/api/settings/health
Alerts API
Manage alert history and alert rules with full support for filtering, acknowledgment, and statistics.
List Alerts
List recent alerts with pagination and extensive filtering options.
Endpoint: GET /api/alerts
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number (1-indexed) |
per_page |
integer | No | 20 | Items per page (1-100) |
alert_type |
string | No | - | Filter by alert type (unexpected_port, drift_detection, cert_expiry, weak_tls, ping_failed) |
severity |
string | No | - | Filter by severity (info, warning, critical) |
acknowledged |
boolean | No | - | Filter by acknowledgment status (true/false) |
scan_id |
integer | No | - | Filter by specific scan ID |
start_date |
string | No | - | Filter alerts after this date (ISO format) |
end_date |
string | No | - | Filter alerts before this date (ISO format) |
Success Response (200 OK):
{
"alerts": [
{
"id": 1,
"scan_id": 42,
"scan_title": "Production Network Scan",
"rule_id": 3,
"alert_type": "cert_expiry",
"severity": "warning",
"message": "Certificate for 192.168.1.10:443 expires in 15 days",
"ip_address": "192.168.1.10",
"port": 443,
"acknowledged": false,
"acknowledged_at": null,
"acknowledged_by": null,
"email_sent": true,
"email_sent_at": "2025-11-18T10:30:00Z",
"webhook_sent": false,
"webhook_sent_at": null,
"created_at": "2025-11-18T10:30:00Z"
}
],
"total": 1,
"page": 1,
"per_page": 20,
"pages": 1
}
Usage Examples:
# List all alerts
curl -X GET http://localhost:5000/api/alerts \
-b cookies.txt
# List only critical unacknowledged alerts
curl -X GET "http://localhost:5000/api/alerts?severity=critical&acknowledged=false" \
-b cookies.txt
# List alerts for a specific scan
curl -X GET "http://localhost:5000/api/alerts?scan_id=42" \
-b cookies.txt
# List alerts from last week
START_DATE=$(date -d '7 days ago' -Iseconds)
curl -X GET "http://localhost:5000/api/alerts?start_date=$START_DATE" \
-b cookies.txt
Acknowledge Alert
Mark an alert as acknowledged.
Endpoint: POST /api/alerts/{alert_id}/acknowledge
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alert_id |
integer | Yes | Alert ID to acknowledge |
Request Body (Optional):
{
"acknowledged_by": "admin"
}
Success Response (200 OK):
{
"status": "success",
"message": "Alert 1 acknowledged",
"acknowledged_by": "admin"
}
Error Responses:
400 Bad Request - Failed to acknowledge:
{
"status": "error",
"message": "Failed to acknowledge alert 1"
}
Usage Example:
curl -X POST http://localhost:5000/api/alerts/1/acknowledge \
-H "Content-Type: application/json" \
-d '{"acknowledged_by":"admin"}' \
-b cookies.txt
List Alert Rules
List all configured alert rules.
Endpoint: GET /api/alerts/rules
Authentication: Required
Success Response (200 OK):
{
"rules": [
{
"id": 1,
"name": "Certificate Expiry Warning",
"rule_type": "cert_expiry",
"enabled": true,
"threshold": 30,
"email_enabled": true,
"webhook_enabled": false,
"severity": "warning",
"filter_conditions": {
"ip_pattern": "192.168.*"
},
"config_id": 1,
"config_title": "Production Scan",
"created_at": "2025-11-01T10:00:00Z",
"updated_at": "2025-11-15T08:30:00Z"
}
],
"total": 1
}
Usage Example:
curl -X GET http://localhost:5000/api/alerts/rules \
-b cookies.txt
Create Alert Rule
Create a new alert rule.
Endpoint: POST /api/alerts/rules
Authentication: Required
Request Body:
{
"name": "Certificate Expiry Warning",
"rule_type": "cert_expiry",
"threshold": 30,
"enabled": true,
"email_enabled": true,
"webhook_enabled": false,
"severity": "warning",
"filter_conditions": {
"ip_pattern": "192.168.*"
},
"config_id": 1
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | User-friendly rule name (defaults to "{rule_type} rule") |
rule_type |
string | Yes | Type of alert (unexpected_port, drift_detection, cert_expiry, weak_tls, ping_failed) |
threshold |
integer | No | Threshold value (e.g., days for cert expiry, percentage for drift) |
enabled |
boolean | No | Whether rule is active (default: true) |
email_enabled |
boolean | No | Send email for this rule (default: false) |
webhook_enabled |
boolean | No | Send webhook for this rule (default: false) |
severity |
string | No | Alert severity: critical, warning, info (default: warning) |
filter_conditions |
object | No | JSON object with filter conditions |
config_id |
integer | No | Config ID to apply rule to (null for all configs) |
Success Response (201 Created):
{
"status": "success",
"message": "Alert rule created successfully",
"rule": {
"id": 1,
"name": "Certificate Expiry Warning",
"rule_type": "cert_expiry",
"enabled": true,
"threshold": 30,
"email_enabled": true,
"webhook_enabled": false,
"severity": "warning",
"filter_conditions": {
"ip_pattern": "192.168.*"
},
"config_id": 1,
"config_title": "Production Scan",
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
}
Error Responses:
400 Bad Request - Missing or invalid fields:
{
"status": "error",
"message": "rule_type is required"
}
400 Bad Request - Invalid rule type:
{
"status": "error",
"message": "Invalid rule_type. Must be one of: unexpected_port, drift_detection, cert_expiry, weak_tls, ping_failed"
}
400 Bad Request - Invalid severity:
{
"status": "error",
"message": "Invalid severity. Must be one of: critical, warning, info"
}
Usage Examples:
# Create certificate expiry rule
curl -X POST http://localhost:5000/api/alerts/rules \
-H "Content-Type: application/json" \
-d '{
"name": "Cert Expiry Warning",
"rule_type": "cert_expiry",
"threshold": 30,
"severity": "warning",
"email_enabled": true
}' \
-b cookies.txt
# Create drift detection rule for specific config
curl -X POST http://localhost:5000/api/alerts/rules \
-H "Content-Type: application/json" \
-d '{
"name": "Production Drift Alert",
"rule_type": "drift_detection",
"threshold": 10,
"severity": "critical",
"config_id": 1,
"email_enabled": true,
"webhook_enabled": true
}' \
-b cookies.txt
Update Alert Rule
Update an existing alert rule.
Endpoint: PUT /api/alerts/rules/{rule_id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
rule_id |
integer | Yes | Alert rule ID |
Request Body:
{
"name": "Updated Rule Name",
"threshold": 60,
"enabled": true,
"email_enabled": true,
"severity": "critical"
}
Note: All fields are optional. Only provided fields will be updated.
Success Response (200 OK):
{
"status": "success",
"message": "Alert rule updated successfully",
"rule": {
"id": 1,
"name": "Updated Rule Name",
"rule_type": "cert_expiry",
"enabled": true,
"threshold": 60,
"email_enabled": true,
"webhook_enabled": false,
"severity": "critical",
"filter_conditions": null,
"config_id": 1,
"config_title": "Production Scan",
"created_at": "2025-11-01T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
}
Error Responses:
404 Not Found - Rule doesn't exist:
{
"status": "error",
"message": "Alert rule 1 not found"
}
400 Bad Request - Invalid severity:
{
"status": "error",
"message": "Invalid severity. Must be one of: critical, warning, info"
}
Usage Example:
# Disable a rule
curl -X PUT http://localhost:5000/api/alerts/rules/1 \
-H "Content-Type: application/json" \
-d '{"enabled":false}' \
-b cookies.txt
# Update threshold and enable email
curl -X PUT http://localhost:5000/api/alerts/rules/1 \
-H "Content-Type: application/json" \
-d '{"threshold":15,"email_enabled":true}' \
-b cookies.txt
Delete Alert Rule
Delete an alert rule. Associated alerts are also deleted via cascade.
Endpoint: DELETE /api/alerts/rules/{rule_id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
rule_id |
integer | Yes | Alert rule ID |
Success Response (200 OK):
{
"status": "success",
"message": "Alert rule 1 deleted successfully"
}
Error Responses:
404 Not Found - Rule doesn't exist:
{
"status": "error",
"message": "Alert rule 1 not found"
}
Usage Example:
curl -X DELETE http://localhost:5000/api/alerts/rules/1 \
-b cookies.txt
Get Alert Statistics
Get alert statistics for a specified time period.
Endpoint: GET /api/alerts/stats
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
days |
integer | No | 7 | Number of days to look back (1-365) |
Success Response (200 OK):
{
"stats": {
"total_alerts": 42,
"unacknowledged_count": 5,
"alerts_by_severity": {
"critical": 3,
"warning": 15,
"info": 24
},
"alerts_by_type": {
"cert_expiry": 10,
"drift_detection": 8,
"unexpected_port": 12,
"weak_tls": 7,
"ping_failed": 5
},
"date_range": {
"start": "2025-11-11T10:00:00Z",
"end": "2025-11-18T10:00:00Z",
"days": 7
}
}
}
Usage Examples:
# Get stats for last 7 days
curl -X GET http://localhost:5000/api/alerts/stats \
-b cookies.txt
# Get stats for last 30 days
curl -X GET "http://localhost:5000/api/alerts/stats?days=30" \
-b cookies.txt
Health Check
Check API health status.
Endpoint: GET /api/alerts/health
Authentication: Not required
Success Response (200 OK):
{
"status": "healthy",
"api": "alerts",
"version": "1.0.0-phase5"
}
Usage Example:
curl -X GET http://localhost:5000/api/alerts/health
Webhooks API
Manage webhook configurations for alert notifications with support for various authentication methods and delivery tracking.
List Webhooks
List all configured webhooks with pagination.
Endpoint: GET /api/webhooks
Authentication: Required
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number (1-indexed) |
per_page |
integer | No | 20 | Items per page (1-100) |
enabled |
boolean | No | - | Filter by enabled status (true/false) |
Success Response (200 OK):
{
"webhooks": [
{
"id": 1,
"name": "Slack Notifications",
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"enabled": true,
"auth_type": "none",
"auth_token": null,
"custom_headers": null,
"alert_types": ["cert_expiry", "drift_detection"],
"severity_filter": ["critical", "warning"],
"timeout": 10,
"retry_count": 3,
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
],
"total": 1,
"page": 1,
"per_page": 20,
"pages": 1
}
Usage Examples:
# List all webhooks
curl -X GET http://localhost:5000/api/webhooks \
-b cookies.txt
# List only enabled webhooks
curl -X GET "http://localhost:5000/api/webhooks?enabled=true" \
-b cookies.txt
Get Webhook
Get details for a specific webhook.
Endpoint: GET /api/webhooks/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Webhook ID |
Success Response (200 OK):
{
"webhook": {
"id": 1,
"name": "Slack Notifications",
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"enabled": true,
"auth_type": "bearer",
"auth_token": "***ENCRYPTED***",
"custom_headers": null,
"alert_types": ["cert_expiry"],
"severity_filter": ["critical"],
"timeout": 10,
"retry_count": 3,
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
}
Error Responses:
404 Not Found - Webhook doesn't exist:
{
"status": "error",
"message": "Webhook 1 not found"
}
Usage Example:
curl -X GET http://localhost:5000/api/webhooks/1 \
-b cookies.txt
Create Webhook
Create a new webhook configuration.
Endpoint: POST /api/webhooks
Authentication: Required
Request Body:
{
"name": "Slack Notifications",
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"enabled": true,
"auth_type": "bearer",
"auth_token": "your-secret-token",
"custom_headers": {
"X-Custom-Header": "value"
},
"alert_types": ["cert_expiry", "drift_detection"],
"severity_filter": ["critical", "warning"],
"timeout": 10,
"retry_count": 3
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Webhook name |
url |
string | Yes | Webhook URL |
enabled |
boolean | No | Whether webhook is enabled (default: true) |
auth_type |
string | No | Authentication type: none, bearer, basic, custom (default: none) |
auth_token |
string | No | Authentication token (encrypted when stored) |
custom_headers |
object | No | JSON object with custom HTTP headers |
alert_types |
array | No | Array of alert types to filter (empty = all types) |
severity_filter |
array | No | Array of severities to filter (empty = all severities) |
timeout |
integer | No | Request timeout in seconds (default: 10) |
retry_count |
integer | No | Number of retry attempts (default: 3) |
template |
string | No | Jinja2 template for custom payload (leave empty for default format) |
template_format |
string | No | Template output format: json, text (default: json) |
content_type_override |
string | No | Custom Content-Type header (auto-detected if not specified) |
Success Response (201 Created):
{
"status": "success",
"message": "Webhook created successfully",
"webhook": {
"id": 1,
"name": "Slack Notifications",
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"enabled": true,
"auth_type": "bearer",
"alert_types": ["cert_expiry"],
"severity_filter": ["critical"],
"custom_headers": null,
"timeout": 10,
"retry_count": 3,
"created_at": "2025-11-18T10:00:00Z"
}
}
Error Responses:
400 Bad Request - Missing required fields:
{
"status": "error",
"message": "name is required"
}
400 Bad Request - Invalid auth type:
{
"status": "error",
"message": "Invalid auth_type. Must be one of: none, bearer, basic, custom"
}
Usage Examples:
# Create webhook with no authentication
curl -X POST http://localhost:5000/api/webhooks \
-H "Content-Type: application/json" \
-d '{
"name": "Slack Notifications",
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"alert_types": ["cert_expiry", "drift_detection"],
"severity_filter": ["critical"]
}' \
-b cookies.txt
# Create webhook with bearer token authentication
curl -X POST http://localhost:5000/api/webhooks \
-H "Content-Type: application/json" \
-d '{
"name": "Custom API",
"url": "https://api.example.com/webhook",
"auth_type": "bearer",
"auth_token": "your-secret-token"
}' \
-b cookies.txt
# Create webhook with custom template
curl -X POST http://localhost:5000/api/webhooks \
-H "Content-Type: application/json" \
-d '{
"name": "Gotify Notifications",
"url": "https://gotify.example.com/message",
"template": "{\"title\": \"{{ scan.title }}\", \"message\": \"{{ alert.message }}\", \"priority\": {% if alert.severity == \"critical\" %}5{% else %}3{% endif %}}",
"template_format": "json",
"auth_type": "custom",
"custom_headers": {"X-Gotify-Key": "your-app-token"}
}' \
-b cookies.txt
Update Webhook
Update an existing webhook configuration.
Endpoint: PUT /api/webhooks/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Webhook ID |
Request Body:
{
"name": "Updated Name",
"enabled": false,
"timeout": 15
}
Note: All fields are optional. Only provided fields will be updated.
Success Response (200 OK):
{
"status": "success",
"message": "Webhook updated successfully",
"webhook": {
"id": 1,
"name": "Updated Name",
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"enabled": false,
"auth_type": "none",
"alert_types": ["cert_expiry"],
"severity_filter": ["critical"],
"custom_headers": null,
"timeout": 15,
"retry_count": 3,
"updated_at": "2025-11-18T11:00:00Z"
}
}
Error Responses:
404 Not Found - Webhook doesn't exist:
{
"status": "error",
"message": "Webhook 1 not found"
}
Usage Example:
# Disable a webhook
curl -X PUT http://localhost:5000/api/webhooks/1 \
-H "Content-Type: application/json" \
-d '{"enabled":false}' \
-b cookies.txt
# Update timeout and retry count
curl -X PUT http://localhost:5000/api/webhooks/1 \
-H "Content-Type: application/json" \
-d '{"timeout":20,"retry_count":5}' \
-b cookies.txt
Delete Webhook
Delete a webhook and all associated delivery logs.
Endpoint: DELETE /api/webhooks/{id}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Webhook ID |
Success Response (200 OK):
{
"status": "success",
"message": "Webhook 1 deleted successfully"
}
Error Responses:
404 Not Found - Webhook doesn't exist:
{
"status": "error",
"message": "Webhook 1 not found"
}
Usage Example:
curl -X DELETE http://localhost:5000/api/webhooks/1 \
-b cookies.txt
Test Webhook
Send a test payload to a webhook to verify configuration.
Endpoint: POST /api/webhooks/{id}/test
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Webhook ID to test |
Success Response (200 OK):
{
"status": "success",
"message": "HTTP 200",
"status_code": 200,
"response_body": "ok"
}
Error Response (failure to connect):
{
"status": "error",
"message": "Connection error: Failed to resolve hostname",
"status_code": null
}
Test Payload Format:
{
"event": "webhook.test",
"message": "This is a test webhook from SneakyScanner",
"timestamp": "2025-11-18T10:00:00Z",
"webhook": {
"id": 1,
"name": "Slack Notifications"
}
}
Usage Example:
curl -X POST http://localhost:5000/api/webhooks/1/test \
-b cookies.txt
Preview Template
Preview a webhook template with sample data before saving.
Endpoint: POST /api/webhooks/preview-template
Authentication: Required
Request Body:
{
"template": "{\"title\": \"{{ scan.title }}\", \"message\": \"{{ alert.message }}\"}",
"template_format": "json"
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
template |
string | Yes | Jinja2 template to preview |
template_format |
string | No | Output format: json or text (default: json) |
Success Response (200 OK):
{
"status": "success",
"rendered": "{\"title\": \"Production Infrastructure Scan\", \"message\": \"Unexpected port 8080 found open on 192.168.1.100\"}",
"format": "json"
}
Error Responses:
400 Bad Request - Template validation error:
{
"status": "error",
"message": "Template validation error: Unexpected '}}' at line 2"
}
400 Bad Request - Template rendering error:
{
"status": "error",
"message": "Template rendering error: undefined variable 'invalid_var'"
}
Usage Example:
curl -X POST http://localhost:5000/api/webhooks/preview-template \
-H "Content-Type: application/json" \
-d '{
"template": "{\"title\": \"Alert\", \"message\": \"{{ alert.message }}\"}",
"template_format": "json"
}' \
-b cookies.txt
Get Template Presets
Get list of available webhook template presets for popular services.
Endpoint: GET /api/webhooks/template-presets
Authentication: Required
Success Response (200 OK):
{
"status": "success",
"presets": [
{
"id": "default_json",
"name": "Default JSON (Current Format)",
"description": "Standard webhook payload format matching the current implementation",
"format": "json",
"content_type": "application/json",
"category": "general",
"template": "{\n \"event\": \"alert.created\",\n \"alert\": {...}\n}"
},
{
"id": "gotify",
"name": "Gotify",
"description": "Optimized for Gotify push notification server with markdown support",
"format": "json",
"content_type": "application/json",
"category": "service",
"template": "{\n \"title\": \"{{ scan.title }}\",\n \"message\": \"{{ alert.message }}\",\n \"priority\": {% if alert.severity == 'critical' %}8{% else %}5{% endif %}\n}"
},
{
"id": "slack",
"name": "Slack",
"description": "Rich Block Kit format for Slack webhooks with visual formatting",
"format": "json",
"content_type": "application/json",
"category": "service",
"template": "..."
}
]
}
Available Presets:
default_json- Standard JSON format (backward compatible)custom_json- Flexible custom format with title/message/prioritygotify- Gotify push notification server formatntfy- Simple text format for Ntfy pub-sub serviceslack- Slack Block Kit format with rich formattingdiscord- Discord embedded message formatplain_text- Simple plain text format
Usage Example:
curl -X GET http://localhost:5000/api/webhooks/template-presets \
-b cookies.txt
Get Webhook Delivery Logs
Get delivery history for a specific webhook.
Endpoint: GET /api/webhooks/{id}/logs
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Webhook ID |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page |
integer | No | 1 | Page number (1-indexed) |
per_page |
integer | No | 20 | Items per page (1-100) |
status |
string | No | - | Filter by status (success/failed) |
Success Response (200 OK):
{
"webhook_id": 1,
"webhook_name": "Slack Notifications",
"logs": [
{
"id": 101,
"alert_id": 42,
"alert_type": "cert_expiry",
"alert_message": "Certificate expires in 15 days",
"status": "success",
"response_code": 200,
"response_body": "ok",
"error_message": null,
"attempt_number": 1,
"delivered_at": "2025-11-18T10:30:00Z"
},
{
"id": 100,
"alert_id": 41,
"alert_type": "drift_detection",
"alert_message": "Port drift detected",
"status": "failed",
"response_code": null,
"response_body": null,
"error_message": "Request timeout after 10 seconds",
"attempt_number": 3,
"delivered_at": "2025-11-18T09:00:00Z"
}
],
"total": 2,
"page": 1,
"per_page": 20,
"pages": 1
}
Error Responses:
404 Not Found - Webhook doesn't exist:
{
"status": "error",
"message": "Webhook 1 not found"
}
Usage Examples:
# Get all delivery logs
curl -X GET http://localhost:5000/api/webhooks/1/logs \
-b cookies.txt
# Get only failed deliveries
curl -X GET "http://localhost:5000/api/webhooks/1/logs?status=failed" \
-b cookies.txt
# Get page 2
curl -X GET "http://localhost:5000/api/webhooks/1/logs?page=2" \
-b cookies.txt
Webhook Payload Format
Default Format
When alerts are triggered, webhooks without custom templates receive JSON payloads in this default format:
{
"event": "alert.created",
"alert": {
"id": 123,
"type": "cert_expiry",
"severity": "warning",
"message": "Certificate for 192.168.1.10:443 expires in 15 days",
"ip_address": "192.168.1.10",
"port": 443,
"acknowledged": false,
"created_at": "2025-11-18T10:30:00Z"
},
"scan": {
"id": 42,
"title": "Production Network Scan",
"timestamp": "2025-11-18T10:00:00Z",
"status": "completed"
},
"rule": {
"id": 3,
"name": "Certificate Expiry Warning",
"type": "cert_expiry",
"threshold": 30
}
}
Custom Templates
Webhooks support custom Jinja2 templates to customize the payload format for different services. Templates have access to the following variables:
Available Template Variables:
| Variable | Description | Example |
|---|---|---|
{{ alert.id }} |
Alert ID | 123 |
{{ alert.type }} |
Alert type | "cert_expiry", "unexpected_port" |
{{ alert.severity }} |
Severity level | "critical", "warning", "info" |
{{ alert.message }} |
Human-readable message | "Certificate expires in 15 days" |
{{ alert.ip_address }} |
IP address | "192.168.1.10" |
{{ alert.port }} |
Port number | 443 |
{{ alert.acknowledged }} |
Is acknowledged | true, false |
{{ alert.created_at }} |
Creation timestamp | datetime object |
{{ scan.id }} |
Scan ID | 42 |
{{ scan.title }} |
Scan title | "Production Network Scan" |
{{ scan.status }} |
Scan status | "completed", "running", "failed" |
{{ scan.duration }} |
Scan duration (seconds) | 125.5 |
{{ scan.triggered_by }} |
How scan was triggered | "manual", "scheduled", "api" |
{{ rule.id }} |
Rule ID | 3 |
{{ rule.name }} |
Rule name | "Certificate Expiry Warning" |
{{ rule.type }} |
Rule type | "cert_expiry" |
{{ rule.threshold }} |
Rule threshold | 30 |
{{ app.name }} |
Application name | "SneakyScanner" |
{{ app.version }} |
Application version | "1.0.0-phase5" |
{{ app.url }} |
Repository URL | "https://github..." |
{{ timestamp }} |
Current UTC timestamp | datetime object |
Jinja2 Features:
- Conditionals:
{% if alert.severity == 'critical' %}...{% endif %} - Filters:
{{ alert.type|upper }},{{ alert.created_at.isoformat() }} - Default values:
{{ alert.port|default('N/A') }}
Template Examples:
Custom JSON with priority:
{
"title": "{{ scan.title }}",
"message": "{{ alert.message }}",
"priority": {% if alert.severity == 'critical' %}5{% elif alert.severity == 'warning' %}3{% else %}1{% endif %},
"alert_id": {{ alert.id }}
}
Plain text format:
ALERT: {{ alert.severity|upper }}
{{ alert.message }}
Scan: {{ scan.title }}
Rule: {{ rule.name }}
Time: {{ timestamp.strftime('%Y-%m-%d %H:%M:%S UTC') }}
Gotify format:
{
"title": "{{ scan.title }}",
"message": "**{{ alert.severity|upper }}**: {{ alert.message }}",
"priority": {% if alert.severity == 'critical' %}8{% elif alert.severity == 'warning' %}5{% else %}2{% endif %},
"extras": {
"client::display": {"contentType": "text/markdown"},
"alert_id": {{ alert.id }}
}
}
Authentication Types
None:
- No authentication headers added
Bearer Token:
- Adds
Authorization: Bearer <token>header - Token is encrypted in database
Basic Authentication:
- Format:
username:passwordin auth_token field - Automatically converts to HTTP Basic Auth
- Credentials encrypted in database
Custom Headers:
- Define any custom HTTP headers
- Useful for API keys or custom authentication schemes
- Example:
{
"X-API-Key": "your-api-key",
"X-Custom-Header": "value"
}
Retry Logic
Failed webhook deliveries are automatically retried with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: After 2 seconds
- Attempt 3: After 4 seconds
- Attempt 4: After 8 seconds
- Maximum delay: 60 seconds
Retry count is configurable per webhook (0-5 attempts).
Health Check
Check API health status.
Endpoint: GET /api/webhooks/health
Authentication: Not required
Success Response (200 OK):
{
"status": "healthy",
"api": "webhooks",
"version": "1.0.0-phase5"
}
Usage Example:
curl -X GET http://localhost:5000/api/webhooks/health
Error Handling
Error Response Format
All error responses follow a consistent JSON format:
{
"error": "Brief error type",
"message": "Detailed error message for debugging"
}
Content Negotiation
The API supports content negotiation based on the request:
- API Requests (Accept: application/json or /api/* path): Returns JSON errors
- Web Requests (Accept: text/html): Returns HTML error pages
Example:
# JSON error response
curl -X GET http://localhost:5000/api/scans/999 \
-H "Accept: application/json" \
-b cookies.txt
# HTML error page
curl -X GET http://localhost:5000/scans/999 \
-H "Accept: text/html" \
-b cookies.txt
Request ID Tracking
Every request receives a unique request ID for tracking and debugging:
Response Headers:
X-Request-ID: a1b2c3d4
X-Request-Duration-Ms: 125
Check application logs for detailed error information using the request ID:
2025-11-14 10:30:15 INFO [a1b2c3d4] GET /api/scans 200 125ms
Status Codes
Success Codes
| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Successful GET, PUT, DELETE requests |
| 201 | Created | Successful POST request that creates a resource |
| 302 | Found | Redirect (used for logout, login success) |
Client Error Codes
| Code | Meaning | Usage |
|---|---|---|
| 400 | Bad Request | Invalid request parameters or body |
| 401 | Unauthorized | Authentication required or failed |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | HTTP method not supported for endpoint |
Server Error Codes
| Code | Meaning | Usage |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error |
Request/Response Examples
Complete Workflow: Trigger and Monitor Scan
#!/bin/bash
# 1. Login and save session
curl -X POST http://localhost:5000/auth/login \
-H "Content-Type: application/json" \
-d '{"password":"yourpassword"}' \
-c cookies.txt
# 2. Trigger a new scan
RESPONSE=$(curl -s -X POST http://localhost:5000/api/scans \
-H "Content-Type: application/json" \
-d '{"config_id":1}' \
-b cookies.txt)
# Extract scan ID from response
SCAN_ID=$(echo $RESPONSE | jq -r '.scan_id')
echo "Scan ID: $SCAN_ID"
# 3. Poll status every 5 seconds until complete
while true; do
STATUS=$(curl -s -X GET http://localhost:5000/api/scans/$SCAN_ID/status \
-b cookies.txt | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" == "completed" ] || [ "$STATUS" == "failed" ]; then
break
fi
sleep 5
done
# 4. Get full scan results
curl -X GET http://localhost:5000/api/scans/$SCAN_ID \
-b cookies.txt | jq '.'
# 5. Logout
curl -X GET http://localhost:5000/auth/logout \
-b cookies.txt
Pagination Example
#!/bin/bash
# Get total number of scans
TOTAL=$(curl -s -X GET "http://localhost:5000/api/scans?per_page=1" \
-b cookies.txt | jq -r '.total')
echo "Total scans: $TOTAL"
# Calculate number of pages (50 items per page)
PER_PAGE=50
PAGES=$(( ($TOTAL + $PER_PAGE - 1) / $PER_PAGE ))
echo "Total pages: $PAGES"
# Fetch all pages
for PAGE in $(seq 1 $PAGES); do
echo "Fetching page $PAGE..."
curl -s -X GET "http://localhost:5000/api/scans?page=$PAGE&per_page=$PER_PAGE" \
-b cookies.txt | jq '.scans[] | {id, timestamp, title, status}'
done
Filter by Status
# Get all running scans
curl -X GET "http://localhost:5000/api/scans?status=running" \
-b cookies.txt | jq '.scans[] | {id, title, started_at}'
# Get all failed scans
curl -X GET "http://localhost:5000/api/scans?status=failed" \
-b cookies.txt | jq '.scans[] | {id, title, error_message}'
# Get all completed scans from last 24 hours
curl -X GET "http://localhost:5000/api/scans?status=completed" \
-b cookies.txt | jq '.scans[] | select(.completed_at > (now - 86400 | todate)) | {id, title, duration}'
Rate Limiting
Currently no rate limiting is implemented. For production deployments, consider:
- Adding nginx rate limiting
- Implementing application-level rate limiting with Flask-Limiter
- Setting connection limits in Gunicorn configuration
Security Considerations
Authentication
- All API endpoints (except
/auth/loginand/api/settings/health) require authentication - Session cookies are httpOnly and secure (in production with HTTPS)
- Passwords are hashed with bcrypt (cost factor 12)
- Sensitive settings values are encrypted at rest
CORS
CORS is configured via environment variable CORS_ORIGINS. Default: * (allow all).
For production, set to specific origins:
CORS_ORIGINS=https://scanner.example.com,https://admin.example.com
HTTPS
For production deployments:
- Use HTTPS (TLS/SSL) for all requests
- Set
SESSION_COOKIE_SECURE=Truein Flask config - Consider using a reverse proxy (nginx) with SSL termination
Input Validation
All inputs are validated:
- Config file paths are checked for existence and valid YAML
- Pagination parameters are sanitized (positive integers, max per_page: 100)
- Scan IDs are validated as integers
- Setting values are type-checked
Versioning
Current Version: 6.0 (Database-Based Configuration)
API versioning will be implemented in future releases. The API is considered stable for Version 6.0 features.
Recent Breaking Changes:
- Phase 2 → Phase 3: Added scan comparison endpoint, schedules API
- Phase 3 → Phase 4: Added configs API, stats API, multiple settings endpoints
- Phase 4 → Phase 5: Full alerts API implementation with filtering, acknowledgment, and statistics
- Phase 5 → Version 6.0: Major - Migrated from file-based configs to database. Added Sites API. Scans now use
config_idinstead ofconfig_file. Alert rules useconfig_idandconfig_title.
Upcoming Changes:
- API versioning with backward compatibility guarantees
API Summary
Implemented APIs (Version 6.0)
- Authentication API - Login, logout, setup
- Sites API - Site definitions with IPs, bulk import from CIDR/list, per-IP settings
- Configs API - Database-based configurations referencing sites
- Scans API - Full CRUD, status polling, comparison
- Schedules API - CRUD operations, manual triggering, APScheduler integration
- Stats API - Trends, summaries, historical data
- Settings API - Application configuration, password management
- Alerts API - Full implementation with filtering, acknowledgment, rules management, and statistics
- Webhooks API - Webhook management, delivery tracking, authentication support, retry logic
Endpoint Count
- Total endpoints: 80+
- Authenticated endpoints: 75+
- Public endpoints: 5 (login, setup, health checks)
Support
For issues, questions, or feature requests:
- GitHub Issues: https://github.com/anthropics/sneakyscanner/issues
- Documentation:
/docs/directory in repository
Last Updated: 2025-11-19 Version: 6.0 - Database-Based Configuration with Sites API Previous: Phase 5 - Alerts Management & Custom Webhook Templates