- 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
24 KiB
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
{
"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
{ "completed": true }
POST /api/v1/setup
Run the first-run setup wizard.
Request
{
"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
{ "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
{ "password": "Hallo123!" }
Note: The frontend SHA256-hashes the password before sending. The backend expects the already-hashed value.
Response 200 — Sets bangui_session cookie.
{ "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
{ "valid": true }
Response 401 — Session missing, expired, or invalid.
POST /api/v1/auth/logout
Revoke the current session.
Response 200
{}
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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{ "jail": "sshd", "ip": "5.6.7.8" }
Response 201
{ "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
{ "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
{ "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
{ "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
{
"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
{
"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
{
"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
{
"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
{ "message": "Jail 'sshd' started.", "jail": "sshd" }
POST /api/v1/jails/{name}/stop
Stop a running jail.
Response 200
{ "message": "Jail 'sshd' stopped.", "jail": "sshd" }
POST /api/v1/jails/{name}/idle
Toggle jail idle mode.
Request body
{ "on": true }
Response 200
{ "message": "Jail 'sshd' idle mode turned on.", "jail": "sshd" }
POST /api/v1/jails/{name}/reload
Reload a single jail.
Response 200
{ "message": "Jail 'sshd' reloaded.", "jail": "sshd" }
POST /api/v1/jails/reload-all
Reload all fail2ban jails.
Response 200
{ "message": "All jails reloaded successfully.", "jail": "*" }
GET /api/v1/jails/{name}/ignoreip
Get the ignore (whitelist) list for a jail.
Response 200
{ "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
{ "ip": "192.168.1.100" }
Response 201
{ "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
{ "ip": "192.168.1.100" }
Response 200
{ "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
{ "on": true }
Response 200
{ "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
{
"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
{
"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):
{
"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
{ "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
{ "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
{
"path": "/var/log/auth.log",
"pattern": "^Failed publickey",
"lines": 50
}
Response 200
{
"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
{
"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
{
"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 (1–2000) |
filter |
string | null | Plain-text substring filter |
Response 200
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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:
{ "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
{
"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
{
"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
{
"total": 20,
"resolved": 18,
"failed": 2
}
Blocklists
GET /api/v1/blocklists
List all blocklist sources.
Response 200
{
"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
{ "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:
{ "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
{
"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
{
"enabled": true,
"interval_hours": 24,
"next_run_at": "2024-12-26T08:00:00Z"
}
PUT /api/v1/blocklists/schedule
Update the import schedule.
Request
{ "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
{
"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
{
"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
{
"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:
{ "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
{ "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 |