Skip to content

Admin API

The Admin API is an HTTP endpoint on the server process for reading and replacing the running configuration, checking readiness, and scraping metrics. pingclair reload and pingclair stop use it. This page describes v0.2.2.

The API exists only when the global admin option is present, or when pingclair run starts without any configuration file. The default address is 127.0.0.1:2019.

{
admin 127.0.0.1:2019
}
http://:8080 {
respond "ok"
}

An occupied admin port prevents startup with failed to bind admin API on ADDR. admin off disables the Admin API listener.

  • With a token, written as admin <address> <token>, every request must send Authorization: Bearer <token>. A missing or incorrect token receives 401 with WWW-Authenticate: Bearer.
  • Without a token, the server logs a warning and admits loopback clients only. Other clients receive 403.
  • origins and enforce_origin in an admin { … } block restrict which browser origins may call the API. A request without an Origin header is admitted unless enforce_origin is set.

Every endpoint below, /live and /ready included, is behind these checks.

Method and path What it does
GET /live 200 while the process runs, draining included.
GET /ready 200 once every listener is bound; 503 before that and from the moment a stop begins.
GET /metrics Prometheus text exposition. Empty when metrics collection is off.
GET /config/[path] Read the running configuration, or one value inside it.
POST, PUT, PATCH, DELETE /config/[path] Change one value and apply the result.
GET … DELETE /id/<id> The same, addressed by an @id field in the document.
POST /load Replace the whole configuration.
POST /adapt Convert a Pingclairfile to the JSON document, without applying it.
POST /stop Stop the process gracefully.
GET /reverse_proxy/upstreams The upstream addresses the configuration names, including those inside handle blocks.
GET /cache Response-cache size against its ceiling.
POST /cache/purge Purge one cached URL. The body is {"host": "…", "path": "…"}.

The configuration document is Pingclair’s own JSON schema, the one pingclair adapt prints. A Caddy document ({"apps": …}) is refused with a message that names the schema this endpoint takes, and that refusal is a deliberate boundary rather than a missing adapter: the two documents share no top-level key and no handler name, so accepting Caddy’s JSON would mean a second configuration surface kept in step with Caddy’s module tree. What POST /load does accept besides its own JSON is a Caddyfile, sent with Content-Type: text/caddyfile — the format operators keep in git.

  • A missing path reads as null. GET /config/<path> for a key or index that does not exist answers 200 with the JSON value null. Writes to a missing path still fail, and the error names the nearest parent.
  • Reads carry a path-qualified Etag. A config write that sends If-Match with the value read from the same path is applied only if the document has not changed since; otherwise it receives 412 and the running document stays unchanged. A write without If-Match is unconditional. This does not extend conditional writes to /load or /adapt.
  • Secrets are masked. Reads show [redacted] in place of the admin token, DNS provider credentials, basic-auth hashes, FastCGI env entries with credential-like names, and header values named Authorization, Proxy-Authorization, Cookie, Set-Cookie, or containing api-key, token, secret, or password. The stored configuration keeps the real values, and a traversal write (PATCH /config/…) edits them in place.
  • A document carrying [redacted] as a secret is refused by /load and POST /config. Restore the original secret values before loading an exported document.
  • Reads keep working during a reload. Each request is answered from one published generation of the document. A write concurrent with another reload may receive 409, or 412 for a conditional write; read again and retry.

A load swaps the configuration atomically: a request sees either the old one or the new one, and a configuration that fails to compile leaves the old one serving. Changes that need new sockets or a new process-wide policy are refused with 409 and restart_required, and nothing is applied:

  • adding or removing a listen address;
  • adding a TLS hostname;
  • changing a startup-fixed global policy, metrics and trusted_proxies included;
  • enabling mutual TLS on a listener that allows session resumption.

A process started without a configuration file is the exception: on Unix, its first /load may add plaintext HTTP listeners. TLS and HTTP/3 listeners need a file at startup. Reloadable process-log settings are not subject to those startup-policy restrictions.

Nothing is collected unless the global metrics option is set; without it, /metrics and a site’s metrics route answer 200 with an empty body. metrics { per_host } adds a host label for the hosts the configuration serves, and folds every other Host into other.

The standard request families use Caddy’s names. The families renamed in 0.2.0:

Before 0.2.0 From 0.2.0
pingclair_requests_total caddy_http_requests_total
pingclair_request_duration_seconds caddy_http_request_duration_seconds
pingclair_request_size_bytes caddy_http_request_size_bytes
pingclair_response_size_bytes caddy_http_response_size_bytes
pingclair_response_duration_seconds caddy_http_response_duration_seconds
pingclair_request_errors_total caddy_http_request_errors_total
pingclair_admin_http_requests_total caddy_admin_http_requests_total
pingclair_reverse_proxy_upstreams_healthy caddy_reverse_proxy_upstreams_healthy

Histogram _bucket, _sum, and _count series follow their family. The old names are no longer exported. Metrics without a Caddy equivalent keep their pingclair_ names: connections, overload, cache, access-log drops, upstream timing, errors and retries, TLS and HTTP/3 counters, readiness, configuration version, queue occupancy, circuit state, and process resources. The labels are Pingclair’s; the names do not imply Caddy’s full label schema.

📚 The CHANGELOG records these changes and their upgrade consequences.