Skip to content

Admin API Reference

The Admin API is a REST interface for managing Dwaar at runtime — add and remove routes, trigger config reloads, purge cache entries, and inspect metrics without restarting the proxy.


The socket path is /var/run/dwaar-admin.sock when you pass --admin-socket without an argument. UDS connections are trusted by the OS: only processes that have read/write permission on the socket file can connect. No Authorization header is required on UDS.

Terminal window
# Start dwaar with the default UDS path
dwaar --admin-socket
# Start dwaar with a custom UDS path
dwaar --admin-socket /run/dwaar/admin.sock
# Call an endpoint over UDS
curl --unix-socket /var/run/dwaar-admin.sock http://localhost/health
curl --unix-socket /var/run/dwaar-admin.sock http://localhost/routes

Worker 0 always binds TCP on 127.0.0.1:6190. This interface requires a bearer token on every authenticated request.

Terminal window
# Health — no token needed
curl http://127.0.0.1:6190/health
# Authenticated request
curl -H "Authorization: Bearer $DWAAR_ADMIN_TOKEN" \
http://127.0.0.1:6190/routes

TCP connections require a bearer token. Set the token via the environment variable DWAAR_ADMIN_TOKEN before starting Dwaar:

Terminal window
export DWAAR_ADMIN_TOKEN="$(openssl rand -hex 32)"
dwaar

If DWAAR_ADMIN_TOKEN is not set, Dwaar starts but rejects all TCP requests with 401. The warning admin API will reject all authenticated requests appears in the log.

Include the token in every TCP request:

Authorization: Bearer <token>

Unix socket connections bypass token authentication — access is controlled by the socket file’s filesystem permissions (mode 0600, owner = the Dwaar process user).

Authenticated requests are subject to a global rate limit of 60 requests per 60-second window. Exceeding the limit returns 429 Too Many Requests. The GET /health endpoint is exempt.


Returns proxy liveness and uptime. No authentication required on either transport.

Response 200 OK

{
"status": "ok",
"uptime_secs": 3742
}
FieldTypeDescription
statusstringAlways "ok" when the process is alive
uptime_secsintegerSeconds since process start

List all active routes in the route table.

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:6190/routes

Response 200 OK

[
{
"domain": "api.example.com",
"upstream": "10.0.0.5:8080",
"tls": false,
"rate_limit_rps": 500,
"under_attack": false,
"source": null
},
{
"domain": "www.example.com",
"upstream": "10.0.0.6:443",
"tls": true,
"rate_limit_rps": null,
"under_attack": false,
"source": "dwaar-ingress"
}
]
FieldTypeDescription
domainstringHostname pattern (lowercase). Wildcard form: *.example.com
upstreamstring|nullUpstream socket address, or null for file-server-only routes
tlsbooleanWhether the proxy connects to the upstream over TLS
rate_limit_rpsinteger|nullPer-IP request rate limit, or null if not set
under_attackbooleanChallenge mode active for this route
sourcestring|nullController that owns this route (e.g. "dwaar-ingress"), or null

Status codes

CodeMeaning
200Route list returned
401Missing or invalid bearer token (TCP only)
429Rate limit exceeded
500Internal serialization error

Add a new route or replace an existing one with the same domain. The domain key is compared case-insensitively.

Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain":"app.example.com","upstream":"10.0.1.10:8080","tls":false}' \
http://127.0.0.1:6190/routes

Request body

{
"domain": "app.example.com",
"upstream": "10.0.1.10:8080",
"tls": false,
"source": "my-controller"
}
FieldTypeRequiredDescription
domainstringyesHostname to route. Wildcards accepted: *.example.com
upstreamstringyesSocket address in host:port form
tlsbooleanyesConnect to upstream with TLS
sourcestringnoController identity tag for ownership tracking

Response 201 Created

{
"domain": "app.example.com",
"upstream": "10.0.1.10:8080",
"tls": false,
"rate_limit_rps": null,
"under_attack": false,
"source": "my-controller"
}

Status codes

CodeMeaning
201Route created or replaced
400Invalid JSON, invalid domain, or invalid upstream address
401Missing or invalid bearer token (TCP only)
413Request body exceeds 64 KB
429Rate limit exceeded

Remove a route by domain. The domain is matched case-insensitively.

Terminal window
curl -X DELETE \
-H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:6190/routes/app.example.com

Response 200 OK

{
"deleted": "app.example.com"
}

Status codes

CodeMeaning
200Route deleted; body contains the deleted domain
400Domain segment is empty
401Missing or invalid bearer token (TCP only)
404No route with that domain exists
429Rate limit exceeded

Serve Prometheus metrics in text exposition format (text/plain; version=0.0.4). Requires Prometheus support to be enabled at startup (enabled by default; disable with --no-metrics).

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:6190/metrics

Response 200 OK — Prometheus text format

# HELP dwaar_requests_total Total requests proxied
# TYPE dwaar_requests_total counter
dwaar_requests_total{domain="api.example.com",status="2xx"} 148203
...

Status codes

CodeMeaning
200Metrics text returned
401Missing or invalid bearer token (TCP only)
404Metrics not enabled; start without --no-metrics=false
429Rate limit exceeded

Return analytics snapshots for all tracked domains as a JSON array. Domains with no traffic since startup are not included.

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:6190/analytics

Response 200 OK

[
{
"domain": "www.example.com",
"page_views_1m": 84,
"page_views_60m": 3902,
...
}
]

See Analytics API for the complete response schema.

Status codes

CodeMeaning
200Array of domain snapshots (empty array if no data)
401Missing or invalid bearer token (TCP only)
429Rate limit exceeded
500Internal serialization error

Return the analytics snapshot for a single domain.

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:6190/analytics/www.example.com

Response 200 OK — see Analytics API for the full schema.

Status codes

CodeMeaning
200Domain snapshot returned
400Domain segment is empty or contains invalid characters
401Missing or invalid bearer token (TCP only)
404No analytics recorded for this domain
429Rate limit exceeded

Invalidate a single cache entry. The key is derived from the host and path segments of the URL. Requires cache storage to be enabled.

Terminal window
curl -X PURGE \
-H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:6190/cache/www.example.com/blog/post-slug"

The key format matches what the proxy stores: {host}/{path} where path begins with /. Leading / is added automatically if absent.

Response 200 OK — entry was found and invalidated

{ "purged": true }

Response 404 Not Found — entry was not in the cache

{ "purged": false, "reason": "not found" }

Status codes

CodeMeaning
200Cache entry invalidated
400Key segment is empty
401Missing or invalid bearer token (TCP only)
404Entry not found in cache
429Rate limit exceeded
501Cache not enabled

Signal Dwaar to re-read the Dwaarfile and atomically swap the route table. Requires the config watcher to be active (default when a Dwaarfile exists). A cooldown of 5 seconds is enforced between consecutive reloads.

Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:6190/reload
# From the CLI (wraps this endpoint)
dwaar reload --admin 127.0.0.1:6190

Response 200 OK

{ "message": "config reload triggered" }

Response 429 Too Many Requests — cooldown not elapsed

{ "error": "reload too soon", "retry_after": 3 }

The Retry-After response header contains the same integer value as retry_after.

Status codes

CodeMeaning
200Reload signal sent to config watcher
401Missing or invalid bearer token (TCP only)
429Cooldown period active; see Retry-After header
501Config watcher not active

All error responses use a consistent JSON envelope:

{ "error": "<human-readable message>" }

The Content-Type is always application/json. The message is safe to display — special characters are escaped via serde_json.

Common errors

Statuserror valueCause
400"invalid JSON: ..."Malformed request body
400"invalid domain: ..."Domain fails validation
400"invalid upstream address: ..."Not a valid host:port
400"missing domain"Empty path segment in DELETE
401"unauthorized"Bearer token absent or wrong
405"method not allowed"Wrong HTTP method for this path; check Allow header
413"request body too large"Body exceeds 64 KB
429"rate limit exceeded"Global 60 req/60 s window
500"serialize error: ..."Internal failure serializing the response
501"reload not supported — config watcher not active"Reload called without watcher
501"cache not enabled"PURGE called without cache backend