Files
SneakyScan/docs/API_REFERENCE.md
Phillip Tarrant 73d04cae5e Update API documentation for database-based configuration
- Fix config_id references to use integers instead of file paths
- Update scan delete response format to include scan_id field
- Add missing read_only field to Settings API responses
- Add missing template fields to Webhook responses
- Correct endpoint count from 80+ to 65+

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 22:06:38 -06:00

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

  1. Authentication
  2. Sites API
  3. Configs API
  4. Scans API
  5. Schedules API
  6. Stats API
  7. Settings API
  8. Alerts API
  9. Webhooks API
  10. Error Handling
  11. Status Codes
  12. 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_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

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/priority
  • gotify - Gotify push notification server format
  • ntfy - Simple text format for Ntfy pub-sub service
  • slack - Slack Block Kit format with rich formatting
  • discord - Discord embedded message format
  • plain_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:password in 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/login and /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:

  1. Use HTTPS (TLS/SSL) for all requests
  2. Set SESSION_COOKIE_SECURE=True in Flask config
  3. 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_id instead of config_file. Alert rules use config_id and config_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:


Last Updated: 2025-11-19 Version: 6.0 - Database-Based Configuration with Sites API Previous: Phase 5 - Alerts Management & Custom Webhook Templates