Reflect config/ vs data/ split, install-time Jinja2 templating, auto-discovered paths, ZeroSSL/ACME support, and the new firewall save-then-apply API in all reference docs.
23 KiB
REST API Reference
All endpoints are served by the management WebUI Flask application bound to 127.0.0.1:9090, proxied through nginx with SSL termination and HTTP basic authentication. Requests target the management domain (e.g., https://<hostname>.local/api/...).
Every request and response uses Content-Type: application/json.
Conventions
Success Responses
{
"ok": true,
"data": <value>
}
The data field contains the payload, which may be an object, array, string, or null.
Error Responses
{
"ok": false,
"error": "<human-readable message>"
}
Error responses carry one of the following HTTP status codes:
| Code | Meaning |
|---|---|
400 |
Bad request — invalid body, missing required field, or malformed value |
404 |
Not found — the requested resource does not exist |
500 |
Internal server error — unexpected failure in the backend |
Firewall API
Endpoints prefixed with /api/firewall/.... Interact with firewalld for zone management, rich rules, NAT, and masquerade.
Zone Management
List All Zones
GET /api/firewall/zones
Returns active zone-to-interface mappings and all available zone definitions.
Response:
| Field | Type | Description |
|---|---|---|
data.active |
object<name, [interface, ...]> |
Currently assigned interfaces per zone |
data.available |
[string, ...] |
All zones known to firewalld |
Get Zone Details
GET /api/firewall/zones/<name>
Return detailed configuration for a single zone.
Response (data):
| Field | Type | Description |
|---|---|---|
target |
string |
Zone target (e.g., "default", "ACCEPT", "REJECT") |
interfaces |
[string, ...] |
Interfaces assigned to this zone |
services |
[string, ...] |
Services allowed through the zone |
ports |
[string, ...] |
Explicit port rules (format: "443/tcp") |
masquerade |
boolean |
Whether masquerade (NAT) is enabled |
forward_ports |
[{port: number, proto: string, toaddr: string, toport: number}, ...] |
Port forward rules |
rich_rules |
[string, ...] |
Rich rule definitions |
Returns HTTP 404 if the zone does not exist.
Create Zone
POST /api/firewall/zones
Create a new firewalld zone.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Zone name |
target |
string |
No | Zone target; defaults to "default" |
Response: data is null on success.
Delete Zone
DELETE /api/firewall/zones/<name>
Remove a zone from firewalld.
Response: data is null on success.
Returns HTTP 404 if the zone does not exist.
Zone Configuration
Set Zone Interfaces
POST /api/firewall/zones/<name>/interfaces
Replace all interfaces assigned to the zone with the provided list.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
interfaces |
[string, ...] |
Yes | List of interface names |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
interfaces |
[string, ...] |
List of interface names now assigned to the zone |
Set Zone Services
POST /api/firewall/zones/<name>/services
Replace all services allowed in the zone with the provided list.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
services |
[string, ...] |
Yes | List of firewalld service names |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
services |
[string, ...] |
List of services now allowed in the zone |
Firewall Rules
Add Rich Rule
POST /api/firewall/rich-rules
Add a firewalld rich rule to a zone.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string |
Yes | Zone to add the rule to |
rule |
string |
Yes | Full rich rule string |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
rule |
string |
Full rich rule string |
Remove Rich Rule
DELETE /api/firewall/rich-rules
Remove an existing rich rule from a zone. The rule string must match exactly.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string |
Yes | Zone the rule belongs to |
rule |
string |
Yes | Exact rich rule string to remove |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
rule |
string |
Exact rich rule string that was removed |
List Rich Rules
GET /api/firewall/rich-rules/<zone>
Return all rich rules for the specified zone.
Response:
| Field | Type | Description |
|---|---|---|
data |
[string, ...] |
Rich rule strings |
NAT
Enable / Disable Masquerade
POST /api/firewall/masquerade
Toggle masquerade (source NAT) for a zone.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string |
Yes | Zone to configure |
enable |
boolean |
Yes | true to enable, false to disable |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
masquerade |
boolean |
Whether masquerade is now enabled for the zone |
Add Port Forward
POST /api/firewall/forward-port
Add a port forwarding rule to a zone.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string |
Yes | Zone to add the rule to |
port |
number |
Yes | External port |
proto |
string |
Yes | Protocol ("tcp" or "udp") |
toaddr |
string |
No | Internal destination address |
toport |
number |
No | Internal destination port |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
port |
number |
External port |
proto |
string |
Protocol ("tcp" or "udp") |
Remove Port Forward
DELETE /api/firewall/forward-port
Remove a port forwarding rule. The body must match the original rule exactly.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string |
Yes | Zone the rule belongs to |
port |
number |
Yes | External port |
proto |
string |
Yes | Protocol ("tcp" or "udp") |
toaddr |
string |
No | Internal destination address |
toport |
number |
No | Internal destination port |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
port |
number |
External port |
proto |
string |
Protocol ("tcp" or "udp") |
Info
Available Services
GET /api/firewall/services
List all service names known to firewalld.
Response:
| Field | Type | Description |
|---|---|---|
data |
[string, ...] |
Service names |
Available Interfaces
GET /api/firewall/interfaces
List all network interfaces currently available on the system.
Response:
| Field | Type | Description |
|---|---|---|
data |
[string, ...] |
Interface names |
DHCP / DNS API
Endpoints prefixed with /api/dhcp/.... Manage dnsmasq configuration, DHCP leases, and custom DNS records.
Configuration
Get Configuration
GET /api/dhcp/config
Return the current DHCP/DNS configuration object.
Response:
| Field | Type | Description |
|---|---|---|
data |
object |
Full dnsmasq configuration dictionary |
Replace Configuration
POST /api/dhcp/config
Replace the entire configuration with the provided JSON object.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (entire body) | object |
Yes | Complete configuration object |
Response: data is null on success.
Partial Update Configuration
PATCH /api/dhcp/config
Deep-merge the provided fields into the existing configuration. Useful for targeted updates (e.g., changing DNS upstream servers without replacing the full config).
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (any subset) | any |
Yes | Fields to merge into the existing config |
Response: data is null on success.
Apply Configuration
POST /api/dhcp/apply
Write the in-memory configuration to /etc/dnsmasq.d/vacuum-wall.conf and reload the dnsmasq service.
Response: data is null on success.
Leases
Get Live Leases
GET /api/dhcp/leases
Return the current DHCP lease table from dnsmasq.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of lease objects |
Add Static Lease
POST /api/dhcp/static-lease
Add a static (reserved) DHCP lease.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
mac |
string |
Yes | MAC address ("aa:bb:cc:dd:ee:ff") |
ip |
string |
Yes | Reserved IP address |
hostname |
string |
No | Hostname for the reservation |
Response (data):
| Field | Type | Description |
|---|---|---|
mac |
string |
MAC address |
ip |
string |
Reserved IP address |
hostname |
string |
Hostname for the reservation |
Remove Static Lease
DELETE /api/dhcp/static-lease?mac=aa:bb:cc:dd:ee:ff
Remove a previously configured static lease.
Query Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
mac |
string |
Yes | MAC address of the lease to remove |
Response: data is null on success.
Returns HTTP 404 if no matching lease is found.
DNS Records
Add DNS Record
POST /api/dhcp/dns-record
Add a custom DNS A record served by dnsmasq.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Fully qualified domain name |
address |
string |
Yes | IP address to resolve to |
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Fully qualified domain name |
address |
string |
IP address |
hostname |
string |
Short hostname |
Remove DNS Record
DELETE /api/dhcp/dns-record?name=nas.lan
Remove a custom DNS record.
Query Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Fully qualified domain name to remove |
Response: data is null on success.
Returns HTTP 404 if no matching record is found.
Proxy API
Endpoints prefixed with /api/proxy/.... Manage reverse proxy domains, nginx configuration generation, and the management WebUI proxy.
Domain Management
List All Domains
GET /api/proxy/domains
Return all configured proxy domains.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of domain configuration objects |
Add Domain
POST /api/proxy/domains
Add a new reverse proxy domain.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Domain name to proxy |
backend_host |
string |
Yes | Backend server IP or hostname |
backend_port |
number |
Yes | Backend server port |
backend_proto |
string |
No | Backend protocol ("http" or "https"); defaults to "http" |
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
Returns HTTP 400 if the domain is already configured.
Get Domain Details
GET /api/proxy/domains/<domain>
Return the configuration for a single proxy domain.
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
backend_host |
string |
Backend server address |
backend_port |
number |
Backend server port |
backend_proto |
string |
Backend protocol |
Returns HTTP 404 if the domain is not configured.
Update Domain
PUT /api/proxy/domains/<domain>
Update one or more fields of an existing domain entry. Only the fields present in the body are modified.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
backend_host |
string |
No | Backend server IP or hostname |
backend_port |
number |
No | Backend server port |
backend_proto |
string |
No | Backend protocol |
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
Returns HTTP 404 if the domain is not configured.
Remove Domain
DELETE /api/proxy/domains/<domain>
Remove a proxy domain and its nginx configuration.
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
Returns HTTP 404 if the domain is not configured.
Apply / Test
Apply Configuration
POST /api/proxy/apply
Regenerate nginx configuration files for all proxy domains and reload the nginx service.
Response: data is null on success.
Returns HTTP 500 if nginx config generation fails or the reload fails.
Test Configuration
POST /api/proxy/test
Run nginx -t against the generated configuration without reloading. Useful for validating changes before applying.
Response:
| Field | Type | Description |
|---|---|---|
data.valid |
boolean |
Whether the configuration syntax is valid |
data.output |
string |
Raw nginx test output |
Management
Configure Management WebUI Proxy
POST /api/proxy/management
Configure the nginx proxy block for the management WebUI itself, including optional HTTP basic authentication.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Management domain (e.g., "myhost.local") |
flask_host |
string |
No | Flask app bind host; defaults to "127.0.0.1" |
flask_port |
number |
No | Flask app bind port; defaults to 9090 |
auth_user |
string |
No | Username for basic auth. An .htpasswd entry is created when this field is present. |
auth_pass |
string |
No | Password for basic auth. Used together with auth_user. |
Response: data is null on success.
If auth_user and auth_pass are provided, the endpoint creates or updates the corresponding .htpasswd file entry.
Certificate API
Endpoints prefixed with /api/certs/.... Manage TLS certificates via ACME (ZeroSSL, Let's Encrypt, etc.).
Listing & Details
List All Certificates
GET /api/certs/list
Return all managed certificates with metadata.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of certificate objects |
Each certificate object:
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain the certificate covers |
expires_at |
string |
Expiration date (ISO 8601) |
days_until_expiry |
number |
Remaining days until expiration |
cert_path |
string |
Path to the certificate file |
key_path |
string |
Path to the private key file |
Get Certificate Details
GET /api/certs/<domain>
Return details for a single certificate.
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain |
expires_at |
string |
Expiration date (ISO 8601) |
days_until_expiry |
number |
Remaining days |
cert_path |
string |
Certificate file path |
key_path |
string |
Private key file path |
Returns HTTP 404 if no certificate is found for the domain.
Operations
Issue Certificate
POST /api/certs/issue
Request a new certificate for a domain.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Domain to issue the certificate for |
webroot |
string |
No | Custom webroot path for HTTP-01 validation |
Response: data is null on success.
Returns HTTP 400 if the domain is missing or the request is malformed. Returns HTTP 500 if the ACME challenge or certificate issuance fails.
Renew Certificate
POST /api/certs/<domain>/renew
Force-renew an existing certificate, regardless of its current expiry status.
Response: data is null on success.
Returns HTTP 404 if the certificate is not found. Returns HTTP 500 if renewal fails.
Remove Certificate
DELETE /api/certs/<domain>
Delete a certificate and remove it from auto-renewal tracking.
Response: data is null on success.
Returns HTTP 404 if the certificate is not found.
Account
Set ACME Contact Email
POST /api/certs/email
Set or update the ACME account contact email (used by the CA for expiration and security notices).
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string |
Yes | Contact email address |
Response (data):
| Field | Type | Description |
|---|---|---|
email |
string |
Contact email address |
WireGuard API
Endpoints prefixed with /api/wireguard/.... Manage the WireGuard VPN server, peers, and client configuration.
Configuration
Get Configuration
GET /api/wireguard/config
Return the current WireGuard server configuration. The private_key field is stripped from the response.
Response:
| Field | Type | Description |
|---|---|---|
data |
object |
Full WireGuard configuration dictionary (private_key omitted) |
Replace Configuration
POST /api/wireguard/config
Replace the entire WireGuard configuration. The private_key field is stripped from the response.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (entire body) | object |
Yes | Complete WireGuard configuration object |
Response:
| Field | Type | Description |
|---|---|---|
data |
object |
Updated configuration (private_key omitted) |
Tunnel Control
Apply Configuration
POST /api/wireguard/apply
Write the current configuration to wg0.conf on disk and bring the WireGuard tunnel up.
Response: data is null on success.
Returns HTTP 500 if config write or interface bring-up fails.
Bring Tunnel Down
POST /api/wireguard/down
Bring down the WireGuard tunnel interface (wg0).
Response: data is null on success.
Status
Tunnel Status
GET /api/wireguard/status
Return live tunnel state, including interface metrics and per-peer connection statistics.
Response (data):
| Field | Type | Description |
|---|---|---|
up |
boolean |
Whether the tunnel interface is up |
interface |
object |
Interface info (listen port, public key, etc.) |
peers |
[object, ...] |
Per-peer connection stats (handshake time, transfer bytes, endpoint, etc.) |
Initialize
POST /api/wireguard/initialize
Perform first-time setup: generate a server key pair, write an initial configuration, and prepare for peer enrollment. This endpoint is idempotent — calling it multiple times has no additional effect.
Response: data is null on success.
Peer Management
Add Peer
POST /api/wireguard/add-peer
Add a new WireGuard peer. A key pair is auto-generated for the peer. The response includes peer details with the private key stripped.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Peer identifier name |
endpoint |
string |
No | Allowed endpoint address ("ip:port") |
allowed_ips |
[string, ...] |
No | Allowed IPs; defaults to ["0.0.0.0/0"] |
persistent_keepalive |
number |
No | Persistent keepalive interval in seconds |
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Peer name |
public_key |
string |
Peer's public key |
allowed_ips |
[string, ...] |
Allowed IPs |
endpoint |
string |
Allowed endpoint |
persistent_keepalive |
number |
Keepalive interval |
Remove Peer
DELETE /api/wireguard/remove-peer?name=alice
Remove a configured peer.
Query Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Peer name to remove |
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Peer name |
Returns HTTP 404 if the peer is not found.
List Peers
GET /api/wireguard/peers
Return all configured peers. Private keys are stripped from the response.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of peer objects (private keys omitted) |
Peer Connection Status
GET /api/wireguard/peer-status
Return live per-peer connection status from wg show, including last handshake time, transfer bytes, and current endpoint.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of live peer status objects |
Client Configuration
Generate Client Config
POST /api/wireguard/generate-client
Generate a complete WireGuard client configuration file for provisioning a device. The returned config includes the peer's private key for the client to use.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Peer name to generate config for |
server_endpoint |
string |
Yes | Server public address ("ip:port") for the client's [Peer] section |
Response (data):
| Field | Type | Description |
|---|---|---|
config |
string |
Complete WireGuard client config text ([Interface] + [Peer] block) |
The client config includes the generated private key so the client can be provisioned directly. Note that this is the only endpoint that returns a WireGuard private key — all other endpoints strip private keys from responses.
Returns HTTP 404 if the peer is not found.