The sites page previously showed total IP count which included duplicates across multiple sites, leading to inflated numbers. Now displays unique IP count as the primary metric with duplicate count shown when present. - Add get_global_ip_stats() method to SiteService for unique/duplicate counts - Update /api/sites?all=true endpoint to include IP statistics - Update sites.html to display unique IPs with optional duplicate indicator - Update API documentation with new response fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
73 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) - Paginated:
{
"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
}
Success Response (200 OK) - All Sites (all=true):
{
"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_ips": 100,
"unique_ips": 85,
"duplicate_ips": 15
}
Response Fields (all=true):
| Field | Type | Description |
|---|---|---|
total_ips |
integer | Total count of IP entries across all sites (including duplicates) |
unique_ips |
integer | Count of distinct IP addresses |
duplicate_ips |
integer | Number of duplicate IP entries (total_ips - unique_ips) |
Usage Example:
# List first page
curl -X GET http://localhost:5000/api/sites \
-b cookies.txt
# Get all sites with global IP stats
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_id": 1,
"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_id": 2,
"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_id": 1,
"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):
{
"scan_id": 42,
"message": "Scan 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
Get Scans by IP
Get the last 10 scans containing a specific IP address.
Endpoint: GET /api/scans/by-ip/{ip_address}
Authentication: Required
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
ip_address |
string | Yes | IP address to search for |
Success Response (200 OK):
{
"ip_address": "192.168.1.10",
"scans": [
{
"id": 42,
"timestamp": "2025-11-14T10:30:00Z",
"duration": 125.5,
"status": "completed",
"title": "Production Network Scan",
"config_id": 1,
"triggered_by": "manual",
"created_at": "2025-11-14T10:30:00Z"
},
{
"id": 38,
"timestamp": "2025-11-13T10:30:00Z",
"duration": 98.2,
"status": "completed",
"title": "Production Network Scan",
"config_id": 1,
"triggered_by": "scheduled",
"created_at": "2025-11-13T10:30:00Z"
}
],
"count": 2
}
Usage Example:
curl -X GET http://localhost:5000/api/scans/by-ip/192.168.1.10 \
-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_id": 1,
"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_id": 1,
"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_id": 1,
"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_id": 1,
"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_id"
}
Usage Example:
curl -X POST http://localhost:5000/api/schedules \
-H "Content-Type: application/json" \
-d '{"name":"Daily Scan","config_id":1,"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_id": 1,
"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_id": 1
}
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",
"read_only": false
}
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,
"template": null,
"template_format": "json",
"content_type_override": null,
"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,
"template": null,
"template_format": "json",
"content_type_override": null,
"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,
"template": null,
"template_format": "json",
"content_type_override": null,
"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,
"template": null,
"template_format": "json",
"content_type_override": null,
"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: 65+
- Authenticated endpoints: 60+
- Public endpoints: 5 (login, setup, health checks for scans/schedules/settings/alerts/webhooks)
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