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).
Method
Path
Purpose
GET
/api/billing/catalog
Plans + prices + purchasable flag (public)
POST
/api/billing/checkout
{ plan } → creates a Razorpay subscription, returns { key_id, subscription_id } for Checkout
GET
/api/billing/subscription
The account’s current subscription + status
POST
/api/billing/cancel
Cancel at cycle end (keeps paid limits until then)
POST
/api/billing/webhook
Razorpay webhook — HMAC-SHA256 verified; flips plan + server limit + re-provisions
POST
/api/admin/accounts/:id/plan
Superadmin — 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.
Method
Path
Purpose
GET
/api/domain
Current custom domain + status (none/pending/active) + CNAME target
PUT
/api/domain
{ domain } → set a custom hostname (status pending)
POST
/api/domain/verify
Check DNS points here, then route + issue a cert
DELETE
/api/domain
Remove the custom domain
GET
/api/export
Download a .tgz of the account’s panel data (SQLite + uploads)
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.
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.