The sites page previously showed total IP count which included duplicates across multiple sites, leading to inflated numbers. Now displays unique IP count as the primary metric with duplicate count shown when present. - Add get_global_ip_stats() method to SiteService for unique/duplicate counts - Update /api/sites?all=true endpoint to include IP statistics - Update sites.html to display unique IPs with optional duplicate indicator - Update API documentation with new response fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
3421 lines
73 KiB
Markdown
3421 lines
73 KiB
Markdown
# 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) - Paginated:**
|
|
```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
|
|
}
|
|
```
|
|
|
|
**Success Response (200 OK) - All Sites (all=true):**
|
|
```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_ips": 100,
|
|
"unique_ips": 85,
|
|
"duplicate_ips": 15
|
|
}
|
|
```
|
|
|
|
**Response Fields (all=true):**
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `total_ips` | integer | Total count of IP entries across all sites (including duplicates) |
|
|
| `unique_ips` | integer | Count of distinct IP addresses |
|
|
| `duplicate_ips` | integer | Number of duplicate IP entries (total_ips - unique_ips) |
|
|
|
|
**Usage Example:**
|
|
```bash
|
|
# List first page
|
|
curl -X GET http://localhost:5000/api/sites \
|
|
-b cookies.txt
|
|
|
|
# Get all sites with global IP stats
|
|
curl -X GET "http://localhost:5000/api/sites?all=true" \
|
|
-b cookies.txt
|
|
```
|
|
|
|
### Get Site
|
|
|
|
Retrieve details for a specific site including all IPs.
|
|
|
|
**Endpoint:** `GET /api/sites/{id}`
|
|
|
|
**Authentication:** Required
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|------|----------|-------------|
|
|
| `id` | integer | Yes | Site ID |
|
|
|
|
**Success Response (200 OK):**
|
|
```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 <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:
|
|
```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
|