Skip to content

API reference

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 URLhttps://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.
  • WebhooksPOST /api/webhooks/:workflowId is public but HMAC-SHA256 verified against the workflow’s secret.

Auth

MethodPathPurpose
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 cookie
POST/api/auth/logoutRevoke the current session
POST/api/auth/change-password{ current, next }

Servers

MethodPathPurpose
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

MethodPathPurpose
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

MethodPathPurpose
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 + deploy
POST/api/pools/:id/sync-lbRe-render + reload the nginx upstream
DELETE/api/pools/:id/members/:serverIdRemove a member

Providers

MethodPathPurpose
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/DELETE/api/hetzner/*Servers (power/rebuild/create/delete), SSH keys, server-types/images/locations
GET/POST/PATCH/DELETE/api/cloudflare/zones/:id/dnsDNS records
GET/POST/DELETE/api/cloudflare/zones/:id/email/*Email Routing — rules, destinations, catch-all

Git providers (GitHub / Gitea)

MethodPathPurpose
GET/api/git/statusConnection status for both providers
PUT/api/git/:provider/tokenConnect ({ token, base_url? })
GET/api/git/:provider/reposList repos
POST/api/git/:provider/reposCreate a repo
GET/POST/DELETE/api/git/:provider/repos/:owner/:repo/keysDeploy keys
GET/POST/DELETE/api/git/:provider/repos/:owner/:repo/hooksWebhooks
GET/POST/DELETE/api/git/:provider/ssh-keysAccount SSH keys

Blog CMS

MethodPathPurpose
GET/api/public/blog/postsPublic — published posts (no auth)
GET/api/public/blog/posts/:slugPublic — one published post
GET/POST/api/blog/postsList (incl. drafts) / create (admin)
PATCH/DELETE/api/blog/posts/:idEdit / delete (admin)
POST/api/blog/posts/:id/publishToggle draft/published

Plan / quota (hosted)

MethodPathPurpose
GET/api/servers/_plan{ plan, maxServers, serverCount, canAddServer, upgradeUrl, managed }

POST /api/servers returns 402 PLAN_LIMIT when the adopted-server cap is hit. Self-hosted instances are unlimited (managed: false).

Billing — control plane (hosted only)

These live on the Mezzanine Cloud control plane (try.mezzanine.cloud), not the per-tenant panel. They’re env-gated on the operator’s RAZORPAY_* keys; the catalog renders even when disabled (enabled: false, plans purchasable: false).

MethodPathPurpose
GET/api/billing/catalogPlans + prices + purchasable flag (public)
POST/api/billing/checkout{ plan } → creates a Razorpay subscription, returns { key_id, subscription_id } for Checkout
GET/api/billing/subscriptionThe account’s current subscription + status
POST/api/billing/cancelCancel at cycle end (keeps paid limits until then)
POST/api/billing/webhookRazorpay webhook — HMAC-SHA256 verified; flips plan + server limit + re-provisions
POST/api/admin/accounts/:id/planSuperadmin — comp a plan with no payment

Checkout returns 503 BILLING_DISABLED when the operator hasn’t configured keys.

Custom domains, export & ops — control plane (hosted only)

Also on the control plane. The first group is tenant-facing; the admin group is superadmin-only.

MethodPathPurpose
GET/api/domainCurrent custom domain + status (none/pending/active) + CNAME target
PUT/api/domain{ domain } → set a custom hostname (status pending)
POST/api/domain/verifyCheck DNS points here, then route + issue a cert
DELETE/api/domainRemove the custom domain
GET/api/exportDownload a .tgz of the account’s panel data (SQLite + uploads)
GET/api/admin/instances/:idInstance detail + account + subscription + recent billing events
GET/api/admin/auditRecent control-plane audit entries
GET/api/admin/billing-eventsRecent billing webhook/checkout events

Onboarding: a freshly provisioned tenant is seeded with its signup identity as the owner admin (MZ_SEED_ADMIN_EMAIL/MZ_SEED_ADMIN_PASSWORD passed by the provisioner), so there’s no first-run setup screen.

Docker (per server)

MethodPathPurpose
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

Terminal window
# 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" \
-d "$BODY"

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.