# 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](#authentication) 2. [Sites API](#sites-api) 3. [Configs API](#configs-api) 4. [Scans API](#scans-api) 5. [Schedules API](#schedules-api) 6. [Stats API](#stats-api) 7. [Settings API](#settings-api) 8. [Alerts API](#alerts-api) 9. [Webhooks API](#webhooks-api) 10. [Error Handling](#error-handling) 11. [Status Codes](#status-codes) 12. [Request/Response Examples](#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:** ```form-data 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:** ```bash # 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:** ```form-data password: "your-password-here" remember: false (optional) ``` **Success Response:** Redirects to dashboard (302) **Error Response:** Returns login page with error message **Usage Example:** ```bash # 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):** ```json { "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:** ```bash # 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):** ```json { "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:** ```bash 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:** ```json { "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):** ```json { "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:** ```bash 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:** ```json { "name": "Updated Name", "description": "Updated description" } ``` **Success Response (200 OK):** ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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:** ```json { "ip_address": "10.0.0.100", "expected_ping": true, "expected_tcp_ports": [22, 443], "expected_udp_ports": [] } ``` **Success Response (201 Created):** ```json { "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:** ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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:** ```json { "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):** ```json { "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:** ```bash 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:** ```json { "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):** ```json { "success": true, "config": {...} } ``` ### Delete Config Delete a configuration. **Endpoint:** `DELETE /api/configs/{id}` **Authentication:** Required **Success Response (200 OK):** ```json { "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:** ```json { "site_id": 5 } ``` **Success Response (200 OK):** ```json { "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):** ```json { "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:** ```json { "config_id": 1 } ``` **Request Body Fields:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `config_id` | integer | Yes | Database config ID | **Success Response (201 Created):** ```json { "scan_id": 42, "status": "running", "message": "Scan queued successfully" } ``` **Error Responses:** *400 Bad Request* - Invalid or missing config_id: ```json { "error": "Invalid request", "message": "config_id is required" } ``` *400 Bad Request* - Config not found: ```json { "error": "Invalid request", "message": "Config with ID 99 not found" } ``` **Usage Example:** ```bash 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):** ```json { "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: ```json { "error": "Invalid pagination parameters", "message": "Page and per_page must be positive integers" } ``` **Usage Examples:** ```bash # 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):** ```json { "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: ```json { "error": "Scan not found", "message": "Scan with ID 42 does not exist" } ``` **Usage Example:** ```bash 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:* ```json { "scan_id": 42, "status": "running", "started_at": "2025-11-14T10:30:00Z", "completed_at": null, "error_message": null } ``` *Completed scan:* ```json { "scan_id": 42, "status": "completed", "started_at": "2025-11-14T10:30:00Z", "completed_at": "2025-11-14T10:32:05Z", "error_message": null } ``` *Failed scan:* ```json { "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: ```json { "error": "Scan not found", "message": "Scan with ID 42 does not exist" } ``` **Usage Example:** ```bash # 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):** ```json { "scan_id": 42, "message": "Scan deleted successfully" } ``` **Error Responses:** *404 Not Found* - Scan doesn't exist: ```json { "error": "Scan not found", "message": "Scan with ID 42 does not exist" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash 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):** ```json { "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: ```json { "error": "Not found", "message": "One or both scans not found" } ``` **Usage Example:** ```bash 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):** ```json { "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):** ```json { "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:** ```bash # 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):** ```json { "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: ```json { "error": "Schedule not found" } ``` **Usage Example:** ```bash 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:** ```json { "name": "Daily Production Scan", "config_id": 1, "cron_expression": "0 2 * * *", "enabled": true } ``` **Success Response (201 Created):** ```json { "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: ```json { "error": "Missing required fields: name, config_id" } ``` **Usage Example:** ```bash 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:** ```json { "name": "Updated Schedule Name", "cron_expression": "0 3 * * *", "enabled": false } ``` **Success Response (200 OK):** ```json { "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: ```json { "error": "Schedule not found" } ``` *400 Bad Request* - Invalid cron expression or validation error: ```json { "error": "Invalid cron expression" } ``` **Usage Example:** ```bash 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):** ```json { "message": "Schedule deleted successfully", "schedule_id": 1 } ``` **Error Responses:** *404 Not Found* - Schedule doesn't exist: ```json { "error": "Schedule not found" } ``` **Usage Example:** ```bash 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):** ```json { "message": "Scan triggered successfully", "schedule_id": 1, "scan_id": 43 } ``` **Error Responses:** *404 Not Found* - Schedule doesn't exist: ```json { "error": "Schedule not found" } ``` **Usage Example:** ```bash 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):** ```json { "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):** ```json { "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: ```json { "error": "days parameter must be at least 1" } ``` **Usage Example:** ```bash # 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):** ```json { "total_scans": 150, "completed_scans": 140, "failed_scans": 5, "running_scans": 5, "scans_today": 3, "scans_this_week": 15 } ``` **Usage Example:** ```bash 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):** ```json { "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: ```json { "error": "Scan not found" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash 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:** ```json { "settings": { "smtp_server": "smtp.example.com", "smtp_port": 587, "retention_days": 90 } } ``` **Success Response (200 OK):** ```json { "status": "success", "message": "Updated 3 settings" } ``` **Error Responses:** *400 Bad Request* - No settings provided: ```json { "status": "error", "message": "No settings provided" } ``` **Usage Example:** ```bash 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):** ```json { "status": "success", "key": "smtp_server", "value": "smtp.gmail.com", "read_only": false } ``` **Error Responses:** *404 Not Found* - Setting doesn't exist: ```json { "status": "error", "message": "Setting \"invalid_key\" not found" } ``` **Usage Example:** ```bash 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:** ```json { "value": "smtp.example.com" } ``` **Success Response (200 OK):** ```json { "status": "success", "message": "Setting \"smtp_server\" updated" } ``` **Error Responses:** *400 Bad Request* - Missing value: ```json { "status": "error", "message": "No value provided" } ``` **Usage Example:** ```bash 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):** ```json { "status": "success", "message": "Setting \"custom_key\" deleted" } ``` **Error Responses:** *404 Not Found* - Setting doesn't exist: ```json { "status": "error", "message": "Setting \"invalid_key\" not found" } ``` **Usage Example:** ```bash 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:** ```json { "password": "newpassword123" } ``` **Success Response (200 OK):** ```json { "status": "success", "message": "Password updated successfully" } ``` **Error Responses:** *400 Bad Request* - Missing or invalid password: ```json { "status": "error", "message": "Password must be at least 8 characters" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash 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):** ```json { "status": "healthy", "api": "settings", "version": "1.0.0-phase1" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash # 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):** ```json { "acknowledged_by": "admin" } ``` **Success Response (200 OK):** ```json { "status": "success", "message": "Alert 1 acknowledged", "acknowledged_by": "admin" } ``` **Error Responses:** *400 Bad Request* - Failed to acknowledge: ```json { "status": "error", "message": "Failed to acknowledge alert 1" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash 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:** ```json { "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):** ```json { "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: ```json { "status": "error", "message": "rule_type is required" } ``` *400 Bad Request* - Invalid rule type: ```json { "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: ```json { "status": "error", "message": "Invalid severity. Must be one of: critical, warning, info" } ``` **Usage Examples:** ```bash # 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:** ```json { "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):** ```json { "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: ```json { "status": "error", "message": "Alert rule 1 not found" } ``` *400 Bad Request* - Invalid severity: ```json { "status": "error", "message": "Invalid severity. Must be one of: critical, warning, info" } ``` **Usage Example:** ```bash # 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):** ```json { "status": "success", "message": "Alert rule 1 deleted successfully" } ``` **Error Responses:** *404 Not Found* - Rule doesn't exist: ```json { "status": "error", "message": "Alert rule 1 not found" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash # 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):** ```json { "status": "healthy", "api": "alerts", "version": "1.0.0-phase5" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash # 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):** ```json { "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: ```json { "status": "error", "message": "Webhook 1 not found" } ``` **Usage Example:** ```bash 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:** ```json { "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):** ```json { "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: ```json { "status": "error", "message": "name is required" } ``` *400 Bad Request* - Invalid auth type: ```json { "status": "error", "message": "Invalid auth_type. Must be one of: none, bearer, basic, custom" } ``` **Usage Examples:** ```bash # 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:** ```json { "name": "Updated Name", "enabled": false, "timeout": 15 } ``` **Note:** All fields are optional. Only provided fields will be updated. **Success Response (200 OK):** ```json { "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: ```json { "status": "error", "message": "Webhook 1 not found" } ``` **Usage Example:** ```bash # 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):** ```json { "status": "success", "message": "Webhook 1 deleted successfully" } ``` **Error Responses:** *404 Not Found* - Webhook doesn't exist: ```json { "status": "error", "message": "Webhook 1 not found" } ``` **Usage Example:** ```bash 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):** ```json { "status": "success", "message": "HTTP 200", "status_code": 200, "response_body": "ok" } ``` **Error Response (failure to connect):** ```json { "status": "error", "message": "Connection error: Failed to resolve hostname", "status_code": null } ``` **Test Payload Format:** ```json { "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:** ```bash 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:** ```json { "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):** ```json { "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: ```json { "status": "error", "message": "Template validation error: Unexpected '}}' at line 2" } ``` *400 Bad Request* - Template rendering error: ```json { "status": "error", "message": "Template rendering error: undefined variable 'invalid_var'" } ``` **Usage Example:** ```bash 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):** ```json { "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:** ```bash 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):** ```json { "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: ```json { "status": "error", "message": "Webhook 1 not found" } ``` **Usage Examples:** ```bash # 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: ```json { "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:* ```jinja2 { "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:* ```jinja2 ALERT: {{ alert.severity|upper }} {{ alert.message }} Scan: {{ scan.title }} Rule: {{ rule.name }} Time: {{ timestamp.strftime('%Y-%m-%d %H:%M:%S UTC') }} ``` *Gotify format:* ```jinja2 { "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 ` 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: ```json { "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):** ```json { "status": "healthy", "api": "webhooks", "version": "1.0.0-phase5" } ``` **Usage Example:** ```bash curl -X GET http://localhost:5000/api/webhooks/health ``` --- ## Error Handling ### Error Response Format All error responses follow a consistent JSON format: ```json { "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:** ```bash # 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 ```bash #!/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 ```bash #!/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 ```bash # 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: ```bash 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: - 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