API
Most of what the dashboard does, it does through an API you can use too - certificates and agents are read-only there, and users are managed rather than created.
GraphQL
Section titled “GraphQL”/api/graphql serves every resource: proxy hosts, L4 hosts, certificates, access lists, users,
groups, agents, settings, the audit log, and a Caddy apply.
curl -sX POST https://cpm.example.com/api/graphql \ -H "Authorization: Bearer $CPM_TOKEN" \ -H 'content-type: application/json' \ -d '{"query":"{ proxyHosts { id name domains enabled } }"}'The schema is introspectable by any authenticated GraphQL client, so your tooling can discover it rather than being told about it.
What is a field, and what is JSON
Section titled “What is a field, and what is JSON”Stable, queryable things are fields: ids, names, domains, timestamps, foreign keys. Configuration
that the model layer owns - load balancing, WAF and geoblock overrides, location rules, mTLS -
travels as a JSON scalar: readable through config on a host, and passed back as input on a
mutation.
That split is deliberate. Those shapes change with the product and are already validated by code that exists; restating them in the schema would be thousands of lines that can drift out of step with the validator while looking authoritative.
Changing many hosts
Section titled “Changing many hosts”bulkProxyHosts and bulkL4ProxyHosts take { action, ids } and return how many hosts changed.
Proxy hosts accept enable, disable, delete, maintenanceOn, maintenanceOff,
setCertificate (with certificateId, null for automatic) and setAccessList (with
accessListId, null for none); L4 hosts the first three. Up to 500 ids per call.
mutation { bulkProxyHosts(input: { action: "disable", ids: [4, 7, 9] })}A batch is all or nothing: an unknown id, or a change one host cannot take, fails the call and
writes nothing. Each host is audited on its own, and Caddy is applied once. The REST equivalents
are POST /api/v1/proxy-hosts/bulk and POST /api/v1/l4-proxy-hosts/bulk.
Authentication
Section titled “Authentication”Bearer tokens, created from Profile → API Tokens in an authenticated dashboard session with an optional expiry. That restriction is deliberate: an existing token cannot mint replacement credentials, so a leaked one cannot extend its own life.
A token carries its owner’s role. The management fields are admin-only, including for an operator - a group grant delegates the dashboard, not the API. Managing your own tokens is the exception, and open to every signed-in role.
The REST API is deprecated
Section titled “The REST API is deprecated”/api/v1/ still works exactly as it did, with the same Bearer tokens and interactive OpenAPI 3.1.0
documentation at /api-docs. It is no longer the documented path and will be removed in a later
release.
Nothing in the field breaks in the meantime: both APIs call the same model functions, so they cannot disagree about what a write does.
Health
Section titled “Health”/api/health is public and unauthenticated - it is what the container health check probes. It also
answers the dashboard host reachability probe when asked with a nonce. Its
boot field is a random id that changes each time the process starts, which is how setup tells a
restart that finished between two checks from one that never happened.