API Reference
PatchPanel’s HTTP API. Used by the React UI, by external automation (billing systems, monitoring exporters, CLI tools), and by Home Assistant when PatchPanel runs as an add-on.
Table of contents
- TOC
Interactive documentation
The full Swagger UI lives at /api-docs on your PatchPanel server. The OpenAPI spec is generated from JSDoc on the route handlers — see packaging/scripts/generate-docs.js.
- Live Swagger UI — interactive testing
- OpenAPI Specification — raw spec for codegen / tooling
Authentication
PatchPanel supports two authentication methods depending on deployment.
1. API keys (programmatic / remote control)
Used by CI pipelines, billing systems, exporters, and CLI tools.
# Bearer token authentication
curl -k -H "Authorization: Bearer YOUR_API_KEY" \
https://your-host:8099/api/state
API keys are bcrypt-hashed at rest. Each key has a permission scope and an optional expiration (max one year). The plaintext key is shown only once at creation — store it in a vault.
2. Session cookies (browser admin UI)
The web UI signs in with username + password (created during the first-run wizard) and receives a session cookie. This auth path is not used by API clients.
Home Assistant add-on mode
When PatchPanel is deployed as an HA add-on, no local authentication runs. HA’s ingress proxy gates access, and PatchPanel reads X-Remote-User-* headers from the proxy for audit attribution. API keys still work via the supervisor’s URL.
Endpoint summary
State
The canonical HAProxy configuration document. Every change re-renders haproxy.cfg, validates with haproxy -c, atomically swaps the file, and reloads.
GET /api/state— Read the current state documentPUT /api/state— Replace the state document (full body, Zod-validated)
HAProxy control
GET /api/haproxy/cfg— Get the on-diskhaproxy.cfg(or?source=stateto render fresh)POST /api/haproxy/reload— Zero-downtime reload via the master CLI socketPOST /api/haproxy/start— Start the HAProxy service (s6 / systemd / direct PID)POST /api/haproxy/stop— Stop the HAProxy service (requires{"confirm": true})GET /api/haproxy/control-strategy— Detect how patchpanel will start/stop HAProxyGET /api/haproxy/ssl-capabilities— Probe HAProxy for supported ciphers / curves / sigalgs
Certificates
GET /api/certificates— Live cert status (loadable / expiring / expired)POST /api/certificates/renew— Renew all certs ({"force": true}ignores expiry)POST /api/certificates/{certId}/renew— Renew one cert
BYO certificates (uploaded PEMs)
GET /api/byo-certs— List uploaded cert directoriesPOST /api/byo-certs/validate— Dry-run PEM validationPOST /api/byo-certs/upload— Upload{name, fullchainPem, privkeyPem}DELETE /api/byo-certs/{name}— Remove cert folder
Trusted CAs
CA bundles for mTLS client validation on binds + upstream verification on backend servers.
GET /api/trusted-cas— List on-disk CA filesPOST /api/trusted-cas/validate— Parse + fingerprint checkPOST /api/trusted-cas/upload— Upload{id, pem}DELETE /api/trusted-cas/{id}— Remove
Trusted CRLs
Certificate revocation lists for binds doing mTLS.
GET /api/trusted-crls— ListPOST /api/trusted-crls/validate— Parse + fingerprintPOST /api/trusted-crls/upload— Upload{id, pem}DELETE /api/trusted-crls/{id}— Remove
Live stats and observability
GET /api/stats— Current HAProxyshow info+show statGET /api/stats/history— Rolling time-window of frontend/backend trafficGET /api/stats/slowest-backends?limit=5— Top N byrtimeGET /api/stats/http-codes— Sampled 1h HTTP status code distributionGET /api/stats/sessions— Active sessions with GeoIP enrichment
Runtime control (HAProxy admin socket)
Mutations that don’t require a config reload.
POST /api/haproxy/servers/{backend}/{server}/state—{"state": "ready"|"drain"|"maint"}POST /api/haproxy/servers/{backend}/{server}/weight—{"weight": 0-256}POST /api/runtime/frontends/{name}/enablePOST /api/runtime/frontends/{name}/disablePOST /api/runtime/maxconn/frontend/{name}—{"max": int}POST /api/runtime/maxconn/global—{"max": int}POST /api/runtime/counters/clear— Reset all max/total countersGET /api/runtime/{acls|maps|tables|resolvers|errors}— Inspect runtime statePOST /api/runtime/sessions/{id}/shutdown— Kill a session
Audit log
GET /api/audit?limit=100&category=cert— Recent state changes
Snapshots
GET /api/snapshots— List time-machine state snapshots
Logs
GET /api/logs— Server-sent-events stream of HAProxy + patchpanel logs
GeoIP
GET /api/geoip/status— DB state, fallback provider, last update
Health
GET /health— Liveness probe (always public, no auth)
Examples
Get the current state
curl -k -H "Authorization: Bearer YOUR_API_KEY" \
https://your-host:8099/api/state | jq .
Replace state and trigger render + reload
# Read, modify, write back. Use jq to add a hostname to an existing ACL.
STATE=$(curl -ks -H "Authorization: Bearer YOUR_API_KEY" \
https://your-host:8099/api/state)
NEXT=$(echo "$STATE" | jq '.acls[0].values += ["newhost.example.com"]')
curl -k -X PUT -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "$NEXT" \
https://your-host:8099/api/state
If the resulting haproxy.cfg fails haproxy -c, the PUT returns 502 with {"output": "<stderr>", "hints": [...]} and no state change.
Force-renew a single certificate
curl -k -X POST -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"force": true}' \
https://your-host:8099/api/certificates/home-mydomain-net/renew
Upload a trusted CA bundle
curl -k -X POST -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"id\":\"corp-root\",\"pem\":\"$(cat corp-root-ca.pem | jq -Rs .)\"}" \
https://your-host:8099/api/trusted-cas/upload
Drain a backend server before maintenance
curl -k -X POST -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"state": "drain"}' \
https://your-host:8099/api/haproxy/servers/home_media/media-01-https/state
Stream live logs
# SSE — keep this curl open
curl -kN -H "Authorization: Bearer YOUR_API_KEY" \
https://your-host:8099/api/logs
Response shape
Success
Most endpoints return application/json with the body as the result directly (no {success: true, data: ...} envelope). Mutating endpoints typically return {ok: true, ...} and an action-specific payload.
Error
{
"error": "HaproxyError",
"message": "haproxy -c failed: code 1",
"output": "[ALERT] (123) : config : parsing [/tmp/...:42] : ...",
"hints": [
{
"severity": "ALERT",
"line": 42,
"message": "no such ACL : 'host_typo'",
"entity": { "kind": "acl", "name": "host_typo" },
"ref": null
}
]
}
output contains HAProxy’s literal stderr; hints are parsed for the UI’s inline error display.
HTTP status codes
200— OK204— No content (idempotent delete)400— Bad request (schema or payload)401— Unauthorized (missing / invalid token)403— Forbidden (token lacks the required permission)404— Not found409— Conflict (state not initialized; stale read; concurrent update)422— Zod schema validation failed (response includesissues)502— HAProxy validation or reload failed (response includesoutput+hints)503— Service unavailable (HAProxy reload mid-flight, retry)500— Internal error
Rate limiting
Auth endpoints and write endpoints are rate-limited. Limits are configurable in /etc/patchpanel/config.yaml > rate_limiting. Response headers:
RateLimit-LimitRateLimit-RemainingRateLimit-Reset
API key permissions
API keys carry an array of permission strings:
read—GETaccess to all endpointsstate-write—PUT /api/state+ write endpointscert-renew— Trigger certificate renewalsruntime-control— Drain servers, set weights, clear countershaproxy-control— Start / stop / reload HAProxy
A key without any write permissions can still read everything.
Best practices
- Always use HTTPS. The default cert is self-signed; use the configured Let’s Encrypt cert for the management UI itself if you can.
- Store API keys in a secrets manager. Never commit them.
- Least privilege. Only grant the permissions an integration needs.
- Expire keys. Set an expiration up to 365 days. Rotate before they expire.
- Monitor the audit log. Every state mutation is recorded with the acting key id.
Related documentation
- Architecture — Components and data flow
- Releases — Versioning + download
Need help? See Support or open an issue.