Everything the panel UI does goes through the same REST API the backend exposes at /api/*. You can drive Mezzanine programmatically with it.
Conventions
Base URL — https://app.yourdomain.com/api
Auth — an HTTP-only cp_session cookie set by POST /api/auth/login. All routes except /api/auth/* and /api/webhooks/* require it.
Roles — non-GET requests need at least moderator; destructive routes need admin. A 403 INSUFFICIENT_ROLE body tells you what’s missing.
Responses — JSON. Mutations return { ok: true, ... }; errors return { error: "message" } with an appropriate status.
Webhooks — POST /api/webhooks/:workflowId is public but HMAC-SHA256 verified against the workflow’s secret.
Auth
Method Path Purpose GET/api/auth/state{ needs_setup, authenticated, user }POST/api/auth/setupCreate the first admin (only when needs_setup) POST/api/auth/login{ username, password, totp? } → sets session cookiePOST/api/auth/logoutRevoke the current session POST/api/auth/change-password{ current, next }
Servers
Method Path Purpose GET/api/serversList adopted servers POST/api/serversAdopt a server { name, host, port, username, auth_type, password? / private_key? } GET/api/servers/:idServer detail + live status PATCH/api/servers/:idUpdate connection / tags DELETE/api/servers/:idRemove GET/api/servers/:id/deploy-keyGet/generate the server’s ed25519 deploy key
Deploy workflows
Method Path Purpose GET/api/workflowsList workflows POST/api/workflowsCreate { name, server_id, definition } GET/api/workflows/:idWorkflow + recent runs POST/api/workflows/:id/runTrigger a manual run → { runId } POST/api/workflows/:id/webhook/rotateMint a new webhook URL + HMAC secret GET/api/deployments/runs/:runIdRun status + per-step output
Pools
Method Path Purpose GET/api/poolsList pools (+ member counts) POST/api/poolsCreate { name, workflow_id?, domain_id?, min_replicas?, max_replicas? } GET/api/pools/:idPool + members PATCH/api/pools/:idUpdate (incl. LB: edge_server_id, lb_strategy, ports) POST/api/pools/:id/scale-out{ server_id } → add member + deployPOST/api/pools/:id/sync-lbRe-render + reload the nginx upstream DELETE/api/pools/:id/members/:serverIdRemove a member
Providers
Method Path Purpose GET/PUT/DELETE/api/hostinger/*Hostinger VPSes, domains, DNS, email, billing GET/POST/PATCH/DELETE/api/contabo/*Contabo instances, snapshots, secrets, usage GET/POST/api/digitalocean/*Droplets + DNS GET/POST/PATCH/DELETE/api/cloudflare/zones/:id/dnsDNS records GET/POST/DELETE/api/cloudflare/zones/:id/email/*Email Routing — rules, destinations, catch-all
Docker (per server)
Method Path Purpose GET/api/servers/:id/docker/containersList containers POST/api/servers/:id/docker/containers/:cid/:actionstart / stop / restart / remove GET/api/servers/:id/docker/imagesList images + update checks
Example: trigger a deploy from CI
# Manual trigger with a session cookie
curl -X POST https://app.yourdomain.com/api/workflows/ $WF_ID /run \
-H ' Content-Type: application/json ' \
--cookie " cp_session= $SESSION "
# Or fire the HMAC-signed webhook (no session needed)
BODY = ' {"ref":"refs/heads/main"} '
SIG = $( printf ' %s ' " $BODY " | openssl dgst -sha256 -hmac " $WEBHOOK_SECRET " | awk ' {print $2} ' )
curl -X POST https://app.yourdomain.com/api/webhooks/ $WF_ID \
-H " Content-Type: application/json " \
-H " X-Hub-Signature-256: sha256= $SIG " \
Notes
This reference covers the most-used route groups, not every endpoint. The authoritative list is the route files in backend/src/routes/ — each maps 1:1 to a /api/<resource> prefix. A generated OpenAPI spec is on the roadmap.