Files
BanGUI/Docs/API-Reference.md
Lukas 69e1726045 Refactor data fetching hooks, add page size lint test
- Simplify useFetchData: remove unused URL building logic
- Add usePolledData initial implementation
- Add router page_size param validation test
- Update API reference docs
- Clean up tasks doc
2026-05-04 06:48:24 +02:00

1339 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BanGUI API Reference
Complete reference for the BanGUI REST API. All endpoints require authentication unless noted as public.
Base URL: `http://{host}:8000`
**Authentication** — All protected endpoints require a valid session cookie (`bangui_session`) or `Authorization: Bearer <token>` header.
---
## Public Endpoints
### `GET /api/v1/health`
Health check. No auth required.
**Response `200`**
```json
{
"status": "ok",
"fail2ban": "online",
"database": "ok",
"scheduler": "running",
"cache": "initialised",
"components": []
}
```
| Field | Description |
|---|---|
| `status` | `ok`, `degraded`, or `unavailable` |
| `fail2ban` | `online` or `offline` |
| `database` | `ok` or `error` |
| `scheduler` | `running`, `stopped`, or `unknown` |
| `cache` | `initialised` or `uninitialised` |
| `components` | List of unhealthy components (empty when `ok`) |
**Response `503**` — fail2ban offline.
---
### `GET /api/v1/setup`
Check whether initial setup has been completed.
**Response `200`**
```json
{ "completed": true }
```
---
### `POST /api/v1/setup`
Run the first-run setup wizard.
**Request**
```json
{
"master_password": "Hallo123!",
"database_path": "/var/lib/fail2ban/fail2ban.sqlite3",
"fail2ban_socket": "/var/run/fail2ban/fail2ban.sock",
"timezone": "Europe/Berlin",
"session_duration_minutes": 480
}
```
| Field | Type | Required | Description |
|---|---|---|---|
| `master_password` | string | Yes | Min 8 chars, uppercase + number + special (`!@#$%^&*()`) |
| `database_path` | string | No | Path to fail2ban DB (default: `/var/lib/fail2ban/fail2ban.sqlite3`) |
| `fail2ban_socket` | string | No | Path to fail2ban socket (default: `/var/run/fail2ban/fail2ban.sock`) |
| `timezone` | string | No | IANA timezone (default: `UTC`) |
| `session_duration_minutes` | int | No | Session TTL in minutes (default: `480`) |
**Response `201`** — Setup completed.
**Response `409`** — Setup already completed.
---
### `GET /api/v1/setup/timezone`
Returns the configured IANA timezone.
**Response `200`**
```json
{ "timezone": "Europe/Berlin" }
```
---
### `GET /metrics`
Prometheus metrics endpoint. No auth required. Returns OpenMetrics text format.
---
## Auth
### `POST /api/v1/auth/login`
Authenticate with the master password.
**Request**
```json
{ "password": "Hallo123!" }
```
> Note: The frontend SHA256-hashes the password before sending. The backend expects the already-hashed value.
**Response `200`** — Sets `bangui_session` cookie.
```json
{ "expires_at": "2024-12-25T10:00:00Z" }
```
**Response `401`** — Invalid password.
**Response `429`** — Too many login attempts (exponential backoff delay). Includes `Retry-After` header.
**Response `503`** — Setup not complete.
---
### `GET /api/v1/auth/session`
Validate the current session.
**Response `200`**
```json
{ "valid": true }
```
**Response `401`** — Session missing, expired, or invalid.
---
### `POST /api/v1/auth/logout`
Revoke the current session.
**Response `200`**
```json
{}
```
Session cookie is cleared. Idempotent — returns `200` even if no session present.
---
## Dashboard
### `GET /api/v1/dashboard/status`
Returns the cached fail2ban server health snapshot (refreshed every 30 seconds).
**Response `200`**
```json
{
"status": {
"version": "0.12.1",
"online": true,
"uptime": 86400,
"jail_count": 3
}
}
```
| Field | Type | Description |
|---|---|---|
| `version` | string | fail2ban server version |
| `online` | bool | Whether fail2ban daemon is reachable |
| `uptime` | int | Daemon uptime in seconds |
| `jail_count` | int | Number of configured jails |
**Response `401`** — Not authenticated.
**Response `502`** — fail2ban unreachable.
---
### `GET /api/v1/dashboard/bans`
Paginated list of recent bans with geo enrichment.
**Query Parameters**
| Param | Type | Default | Description |
|---|---|---|---|
| `range` | `TimeRange` | `24h` | Time window: `24h`, `7d`, `30d`, `365d` |
| `source` | string | `fail2ban` | Data source: `fail2ban` or `archive` |
| `page` | int | `1` | 1-based page number |
| `page_size` | int | `100` | Items per page (max 500) |
| `origin` | string | null | Filter: `blocklist` or `selfblock` |
**Response `200`**
```json
{
"items": [
{
"ip": "1.2.3.4",
"jail": "sshd",
"banned_at": "2024-12-25T08:00:00Z",
"expires_at": "2024-12-26T08:00:00Z",
"country": "US",
"asn": "AS15169",
"org": "Google LLC"
}
],
"total": 150,
"page": 1,
"page_size": 100
}
```
---
### `GET /api/v1/dashboard/bans/by-country`
Ban counts aggregated by country.
**Query Parameters** — Same as `/bans` plus optional `country_code` filter.
**Response `200`**
```json
{
"countries": { "US": 45, "CN": 32, "BR": 18 },
"total": 150,
"items": [...]
}
```
---
### `GET /api/v1/dashboard/bans/trend`
Ban counts grouped into time buckets for charts.
**Bucket sizes:**
- `24h` → 1-hour buckets (24 total)
- `7d` → 6-hour buckets (28 total)
- `30d` → 1-day buckets (30 total)
- `365d` → 7-day buckets (~53 total)
**Query Parameters** — Same as `/bans`.
**Response `200`**
```json
{
"buckets": [
{ "ts": "2024-12-24T00:00:00Z", "count": 12 },
{ "ts": "2024-12-24T01:00:00Z", "count": 8 }
],
"bucket_size": "1h",
"total": 150
}
```
---
### `GET /api/v1/dashboard/bans/by-jail`
Ban counts grouped by jail.
**Query Parameters** — Same as `/bans`.
**Response `200`**
```json
{
"jails": [
{ "jail": "sshd", "count": 120 },
{ "jail": "nginx-http-auth", "count": 30 }
],
"total": 150
}
```
---
## Bans
### `GET /api/v1/bans/active`
List all currently banned IPs across all jails.
**Response `200`**
```json
{
"items": [
{
"ip": "1.2.3.4",
"jail": "sshd",
"banned_at": "2024-12-25T08:00:00Z",
"expires_at": "2024-12-26T08:00:00Z",
"country": "US"
}
],
"total": 42
}
```
**Response `401`** — Not authenticated.
**Response `502`** — fail2ban unreachable.
---
### `POST /api/v1/bans`
Ban an IP address in a specific jail.
**Request**
```json
{ "jail": "sshd", "ip": "5.6.7.8" }
```
**Response `201`**
```json
{ "message": "IP '5.6.7.8' banned in jail 'sshd'.", "jail": "sshd" }
```
**Response `400`** — Invalid IP address.
**Response `404`** — Jail not found.
**Response `409`** — Ban command failed in fail2ban.
**Response `429`** — Rate limit exceeded (10 ban requests/minute per IP).
**Response `502`** — fail2ban unreachable.
---
### `DELETE /api/v1/bans`
Unban an IP from one or all jails.
**Request**
```json
{ "ip": "5.6.7.8", "jail": "sshd", "unban_all": false }
```
| Field | Type | Required | Description |
|---|---|---|---|
| `ip` | string | Yes | IP address to unban |
| `jail` | string | No | Specific jail to unban from |
| `unban_all` | bool | No | `true` = unban from all jails (default: `false` if `jail` omitted) |
**Response `200`**
```json
{ "message": "IP '5.6.7.8' unbanned from jail 'sshd'.", "jail": "sshd" }
```
**Response `404`** — Jail not found.
**Response `429`** — Rate limit exceeded (10 unban requests/minute per IP).
---
### `DELETE /api/v1/bans/all`
Unban every currently banned IP across all jails.
**Response `200`**
```json
{ "message": "All bans cleared. 42 IP addresses unbanned.", "count": 42 }
```
---
## History
### `GET /api/v1/history`
Paginated historical ban records.
**Query Parameters**
| Param | Type | Default | Description |
|---|---|---|---|
| `range` | `TimeRange` | null | Time filter: `24h`, `7d`, `30d`, `365d` (null = all-time) |
| `jail` | string | null | Filter by jail name (exact match) |
| `ip` | string | null | Filter by IP prefix |
| `origin` | string | null | Filter: `blocklist` or `selfblock` |
| `source` | string | `fail2ban` | `fail2ban` or `archive` |
| `page` | int | `1` | 1-based page number |
| `page_size` | int | `100` | Items per page (max 500) |
**Response `200`**
```json
{
"items": [
{
"ip": "1.2.3.4",
"jail": "sshd",
"banned_at": "2024-12-25T08:00:00Z",
"unbanned_at": "2024-12-26T08:00:00Z",
"origin": "selfblock",
"country": "US"
}
],
"total": 500,
"page": 1,
"page_size": 100
}
```
---
### `GET /api/v1/history/archive`
Same as `/history` but reads from the archive database.
**Query Parameters** — Same as `/history` (no `origin` filter).
---
### `GET /api/v1/history/{ip}`
Complete ban timeline for a single IP.
**Response `200`**
```json
{
"ip": "1.2.3.4",
"country": "US",
"total_bans": 5,
"timeline": [
{
"jail": "sshd",
"banned_at": "2024-12-25T08:00:00Z",
"unbanned_at": "2024-12-26T08:00:00Z",
"origin": "selfblock"
}
]
}
```
**Response `404`** — No history found for this IP.
---
## Jails
### `GET /api/v1/jails`
List all active fail2ban jails.
**Response `200`**
```json
{
"items": [
{
"name": "sshd",
"enabled": true,
"currently_banned": 12,
"total_bans": 150,
"failed_attempts": 320,
"find_time": 600,
"ban_time": 86400,
"max_retries": 5,
"backend": "polling",
"idle": false
}
],
"total": 3
}
```
---
### `GET /api/v1/jails/{name}`
Full detail for a single jail.
**Response `200`**
```json
{
"name": "sshd",
"enabled": true,
"log_paths": ["/var/log/auth.log"],
"fail_regex": ["^%(__prefix_line)sFailed publickey forInvalid user"],
"ignore_regex": [],
"date_pattern": null,
"log_encoding": "UTF-8",
"actions": ["iptables"],
"find_time": 600,
"ban_time": 86400,
"max_retries": 5,
"ignore_list": ["192.168.1.1"],
"ignore_self": true,
"currently_banned": 12,
"total_bans": 150,
"failed_attempts": 320
}
```
**Response `404`** — Jail not found.
---
### `POST /api/v1/jails/{name}/start`
Start a stopped jail.
**Response `200`**
```json
{ "message": "Jail 'sshd' started.", "jail": "sshd" }
```
---
### `POST /api/v1/jails/{name}/stop`
Stop a running jail.
**Response `200`**
```json
{ "message": "Jail 'sshd' stopped.", "jail": "sshd" }
```
---
### `POST /api/v1/jails/{name}/idle`
Toggle jail idle mode.
**Request body**
```json
{ "on": true }
```
**Response `200`**
```json
{ "message": "Jail 'sshd' idle mode turned on.", "jail": "sshd" }
```
---
### `POST /api/v1/jails/{name}/reload`
Reload a single jail.
**Response `200`**
```json
{ "message": "Jail 'sshd' reloaded.", "jail": "sshd" }
```
---
### `POST /api/v1/jails/reload-all`
Reload all fail2ban jails.
**Response `200`**
```json
{ "message": "All jails reloaded successfully.", "jail": "*" }
```
---
### `GET /api/v1/jails/{name}/ignoreip`
Get the ignore (whitelist) list for a jail.
**Response `200`**
```json
{ "items": ["192.168.1.0/24", "10.0.0.1"], "total": 2 }
```
---
### `POST /api/v1/jails/{name}/ignoreip`
Add an IP or CIDR to the ignore list.
**Request**
```json
{ "ip": "192.168.1.100" }
```
**Response `201`**
```json
{ "message": "IP '192.168.1.100' added to ignore list of jail 'sshd'.", "jail": "sshd" }
```
**Response `400`** — Invalid IP or network.
---
### `DELETE /api/v1/jails/{name}/ignoreip`
Remove an IP or CIDR from the ignore list.
**Request**
```json
{ "ip": "192.168.1.100" }
```
**Response `200`**
```json
{ "message": "IP '192.168.1.100' removed from ignore list of jail 'sshd'.", "jail": "sshd" }
```
---
### `POST /api/v1/jails/{name}/ignoreself`
Toggle the `ignoreself` option (ban server's own IP).
**Request**
```json
{ "on": true }
```
**Response `200`**
```json
{ "message": "ignoreself enabled for jail 'sshd'.", "jail": "sshd" }
```
---
### `GET /api/v1/jails/{name}/banned`
Paginated currently-banned IPs for a specific jail.
**Query Parameters**
| Param | Type | Default | Description |
|---|---|---|---|
| `page` | int | `1` | 1-based page number |
| `page_size` | int | `100` | Items per page (max 100) |
| `search` | string | null | Case-insensitive substring filter on IP |
**Response `200`**
```json
{
"items": [
{
"ip": "1.2.3.4",
"banned_at": "2024-12-25T08:00:00Z",
"expires_at": "2024-12-26T08:00:00Z",
"country": "US",
"asn": "AS15169",
"org": "Google LLC"
}
],
"total": 12,
"page": 1,
"page_size": 100
}
```
---
## Config
### `GET /api/v1/config/global`
Get global fail2ban settings.
**Response `200`**
```json
{
"loglevel": "INFO",
"logtarget": "/var/log/fail2ban.log",
"syslog_socket": "auto",
"db_file": "/var/lib/fail2ban/fail2ban.sqlite3",
"db_purge_age": 86400
}
```
---
### `PUT /api/v1/config/global`
Update global fail2ban settings.
**Request** — All fields optional (only non-null fields written):
```json
{
"loglevel": "DEBUG",
"logtarget": "/var/log/fail2ban.log",
"db_purge_age": 604800
}
```
| Field | Type | Description |
|---|---|---|
| `loglevel` | string | `CRITICAL`, `ERROR`, `WARNING`, `NOTICE`, `INFO`, `DEBUG` |
| `logtarget` | string | `STDOUT`, `STDERR`, `SYSLOG`, or a file path |
| `db_purge_age` | int | Seconds before old ban records are purged |
**Response `204`** — Updated.
**Response `400`** — `logtarget` invalid (not in allowed directories).
**Response `429`** — Rate limit exceeded (10 updates/minute per IP).
---
### `POST /api/v1/config/reload`
Trigger a full fail2ban reload.
**Response `204`**
---
### `POST /api/v1/config/restart`
Restart the fail2ban service (stop + start).
**Response `204`**
**Response `503`** — fail2ban did not come back online within 10 seconds.
---
### `POST /api/v1/config/regex-test`
Test a fail regex pattern against a sample log line (stateless, no fail2ban call).
**Request**
```json
{ "pattern": "^%(__prefix_line)sFailed publickey", "sample": "Dec 25 08:00:01 server sshd[123]: Failed publickey for user admin from 1.2.3.4" }
```
**Response `200`**
```json
{ "matched": true, "groups": ["Dec 25 08:00:01", "server", "123", "admin", "1.2.3.4"] }
```
---
### `POST /api/v1/config/preview-log`
Read a log file and test a regex against each line.
**Request**
```json
{
"path": "/var/log/auth.log",
"pattern": "^Failed publickey",
"lines": 50
}
```
**Response `200`**
```json
{
"lines": [
{ "line": "Dec 25 08:00:01 server sshd[123]: Failed publickey...", "matched": true, "groups": [...] },
{ "line": "Dec 25 08:00:02 server sshd[456]: Accepted publickey...", "matched": false }
]
}
```
---
### `GET /api/v1/config/map-color-thresholds`
Get map color threshold configuration.
**Response `200`**
```json
{
"thresholds": [
{ "count": 0, "color": "#4ade80" },
{ "count": 10, "color": "#facc15" },
{ "count": 50, "color": "#f97316" },
{ "count": 200, "color": "#ef4444" }
]
}
```
---
### `PUT /api/v1/config/map-color-thresholds`
Update map color thresholds.
**Request**
```json
{
"thresholds": [
{ "count": 0, "color": "#4ade80" },
{ "count": 100, "color": "#facc15" }
]
}
```
> Thresholds must be strictly ascending by `count`.
**Response `200`** — Updated thresholds.
**Response `400`** — Thresholds not properly ordered.
---
### `GET /api/v1/config/fail2ban-log`
Read the tail of the fail2ban daemon log file.
**Query Parameters**
| Param | Type | Default | Description |
|---|---|---|---|
| `lines` | int | `200` | Number of tail lines (12000) |
| `filter` | string | null | Plain-text substring filter |
**Response `200`**
```json
{
"lines": ["2024-12-25 08:00:01,000 INFO ...", "2024-12-25 08:00:02,000 WARNING ..."],
"count": 2
}
```
---
### `GET /api/v1/config/service-status`
Fail2ban service health with log configuration.
**Response `200`**
```json
{
"online": true,
"version": "0.12.1",
"loglevel": "INFO",
"logtarget": "/var/log/fail2ban.log"
}
```
---
## Filters
### `GET /api/v1/config/filters`
List all available filters with active/inactive status.
**Response `200`**
```json
{
"items": [
{
"name": "sshd",
"active": true,
"used_by_jails": ["sshd"],
"source_file": "/etc/fail2ban/filter.d/sshd.conf",
"has_local_override": false,
"failregex": ["^%(__prefix_line)sFailed publickey"],
"ignoreregex": [],
"date_pattern": null,
"journalmatch": null
}
],
"total": 12
}
```
Active filters (used by running jails) are listed first, sorted alphabetically. Inactive filters follow.
---
### `GET /api/v1/config/filters/{name}`
Full detail for a single filter.
**Response `200`** — FilterConfig object (same shape as list item).
**Response `404`** — Filter not found in `filter.d/`.
---
### `POST /api/v1/config/filters`
Create a new user-defined filter.
**Request**
```json
{
"name": "nginx-404",
"failregex": ["^\\s*\\S+ \\S+ \\S+ GET /nonexistent"],
"ignoreregex": null,
"date_pattern": null,
"journalmatch": null
}
```
**Response `201`** — Created FilterConfig object.
**Response `409`** — Filter with this name already exists.
**Response `422`** — Regex failed to compile.
**Response `429`** — Rate limit exceeded (5 creates/minute per IP).
---
### `PUT /api/v1/config/filters/{name}`
Update a filter's `.local` override. Only non-null fields are written.
**Request**
```json
{
"failregex": ["^new pattern here"],
"ignoreregex": null
}
```
**Query Parameter** — `reload` (bool, default `false`) — trigger fail2ban reload after writing.
**Response `200`** — Updated FilterConfig object.
**Response `422`** — Regex failed to compile.
**Response `429`** — Rate limit exceeded (10 updates/minute per IP).
---
### `DELETE /api/v1/config/filters/{name}`
Delete a user-created filter's `.local` file. Shipped `.conf`-only filters cannot be deleted.
**Response `204`**
**Response `409`** — Filter is a shipped default (conf-only).
---
## Actions
### `GET /api/v1/config/actions`
List all available actions with active/inactive status.
**Response `200`**
```json
{
"actions": [
{
"name": "iptables",
"active": true,
"used_by_jails": ["sshd", "nginx-http-auth"],
"source_file": "/etc/fail2ban/action.d/iptables.conf",
"has_local_override": false,
"start_command": "iptables -N f2b-sshd...",
"stop_command": "iptables -X f2b-sshd...",
"check_command": "iptables -L f2b-sshd -n",
"ban_action": "iptables -I f2b-sshd...",
"unban_action": "iptables -D f2b-sshd..."
}
],
"total": 8
}
```
---
### `GET /api/v1/config/actions/{name}`
Full detail for a single action.
**Response `200`** — ActionConfig object (same shape as list item).
---
### `POST /api/v1/config/actions`
Create a new user-defined action.
**Request**
```json
{
"name": "my-custom-action",
"start_command": "echo 'starting'",
"stop_command": "echo 'stopping'",
"check_command": "echo 'checking'",
"ban_action": "echo 'banning'",
"unban_action": "echo 'unbanning'"
}
```
**Response `201`** — Created ActionConfig object.
**Response `409`** — Action with this name already exists.
---
### `PUT /api/v1/config/actions/{name}`
Update an action's `.local` override.
**Request** — All fields optional:
```json
{ "ban_action": "new ban command here" }
```
**Query Parameter** — `reload` (bool, default `false`).
**Response `200`** — Updated ActionConfig object.
---
### `DELETE /api/v1/config/actions/{name}`
Delete a user-created action's `.local` file.
**Response `204`**
**Response `409`** — Action is a shipped default (conf-only).
---
## Geo
### `GET /api/v1/geo/lookup/{ip}`
Ban status and geo info for an IP.
**Response `200`**
```json
{
"ip": "1.2.3.4",
"banned": true,
"jails": ["sshd", "nginx-http-auth"],
"country": "US",
"country_name": "United States",
"region": "North America",
"city": "Mountain View",
"isp": "Google LLC",
"asn": "AS15169",
"org": "Google LLC",
"last_ban": "2024-12-25T08:00:00Z",
"total_bans": 3
}
```
**Response `400`** — Invalid IP address.
---
### `GET /api/v1/geo/stats`
Geo cache diagnostic counters.
**Response `200`**
```json
{
"total": 1500,
"resolved": 1480,
"failed": 20,
"cache_size": 1480
}
```
---
### `POST /api/v1/geo/re-resolve`
Re-resolve all IPs with failed geo lookups.
**Response `200`**
```json
{
"total": 20,
"resolved": 18,
"failed": 2
}
```
---
## Blocklists
### `GET /api/v1/blocklists`
List all blocklist sources.
**Response `200`**
```json
{
"sources": [
{
"id": 1,
"name": "Country Block List",
"url": "https://example.com/blocklist.txt",
"enabled": true,
"last_import_at": "2024-12-25T08:00:00Z",
"last_import_succeeded": true,
"last_import_ban_count": 45
}
],
"total": 1
}
```
---
### `POST /api/v1/blocklists`
Add a new blocklist source.
**Request**
```json
{ "name": "Spamhaus DROP", "url": "https://www.spamhaus.org/drop/drop.txt", "enabled": true }
```
**Response `201`** — Created BlocklistSource object.
**Response `400`** — URL validation failed.
---
### `GET /api/v1/blocklists/{source_id}`
Get a single blocklist source.
---
### `PUT /api/v1/blocklists/{source_id}`
Update a blocklist source.
**Request** — All fields optional:
```json
{ "name": "New Name", "enabled": false }
```
---
### `DELETE /api/v1/blocklists/{source_id}`
Delete a blocklist source.
**Response `204`**
---
### `POST /api/v1/blocklists/import`
Trigger an immediate import of all enabled blocklist sources.
**Response `200`**
```json
{
"started_at": "2024-12-25T10:00:00Z",
"sources": [
{
"id": 1,
"name": "Spamhaus DROP",
"url": "https://www.spamhaus.org/drop/drop.txt",
"imported": 45,
"skipped": 3,
"failed": false,
"error": null
}
],
"total_imported": 45,
"total_skipped": 3,
"total_failed": 0
}
```
**Response `429`** — Rate limit exceeded (1 import/hour per IP).
---
### `GET /api/v1/blocklists/schedule`
Get the current import schedule.
**Response `200`**
```json
{
"enabled": true,
"interval_hours": 24,
"next_run_at": "2024-12-26T08:00:00Z"
}
```
---
### `PUT /api/v1/blocklists/schedule`
Update the import schedule.
**Request**
```json
{ "enabled": true, "interval_hours": 12 }
```
**Response `200`** — Updated ScheduleInfo.
---
### `GET /api/v1/blocklists/log`
Paginated import log.
**Query Parameters**
| Param | Type | Default | Description |
|---|---|---|---|
| `source_id` | int | null | Filter by source |
| `page` | int | `1` | 1-based page |
| `page_size` | int | `100` | Items per page (max 500) |
**Response `200`**
```json
{
"items": [
{
"id": 1,
"source_id": 1,
"source_name": "Spamhaus DROP",
"started_at": "2024-12-25T08:00:00Z",
"completed_at": "2024-12-25T08:01:23Z",
"imported": 45,
"skipped": 3,
"failed": false,
"error": null
}
],
"total": 50,
"page": 1,
"page_size": 100
}
```
---
### `GET /api/v1/blocklists/{source_id}/preview`
Preview the contents of a blocklist source (downloads and samples first ~20 lines).
**Response `200`**
```json
{
"url": "https://example.com/blocklist.txt",
"validated_lines": ["1.2.3.4", "5.6.7.8"],
"invalid_lines": ["not-an-ip"],
"total_valid": 2,
"total_invalid": 1
}
```
**Response `502`** — URL could not be reached.
---
## Server
### `GET /api/v1/server/settings`
Get fail2ban server-level settings.
**Response `200`**
```json
{
"loglevel": "INFO",
"logtarget": "/var/log/fail2ban.log",
"syslog_socket": "auto",
"db_file": "/var/lib/fail2ban/fail2ban.sqlite3",
"db_purge_age": 86400,
"max_matches": 100
}
```
---
### `PUT /api/v1/server/settings`
Update fail2ban server-level settings.
**Request** — All fields optional:
```json
{ "loglevel": "DEBUG", "max_matches": 200 }
```
**Response `204`**
**Response `400`** — fail2ban rejected a setting.
---
### `POST /api/v1/server/flush-logs`
Flush and re-open fail2ban log files (after log rotation).
**Response `200`**
```json
{ "message": "Success: 1 log(s) flushed" }
```
---
## Common Types
### `TimeRange`
```
"24h" | "7d" | "30d" | "365d"
```
### `BanOrigin`
```
"blocklist" | "selfblock"
```
### `Source`
```
"fail2ban" | "archive"
```
---
## Status Codes
| Code | Meaning |
|---|---|
| `200` | OK |
| `201` | Created |
| `204` | No Content |
| `400` | Bad Request — invalid input |
| `401` | Unauthorized — session missing, expired, or invalid |
| `404` | Not Found |
| `409` | Conflict |
| `422` | Unprocessable Entity — validation failed |
| `429` | Too Many Requests — rate limit exceeded |
| `502` | Bad Gateway — fail2ban unreachable |
| `503` | Service Unavailable |