API Reference

Everything an external billing system (WHMCS or similar) needs to provision hosting accounts and manage databases, email, FTP, and files on a customer's behalf.

Overview

The ZWPanel API is a REST API running on every ZWPanel node, used by the dashboard itself and available to external systems such as a billing platform. All endpoints are versioned under a single base path:

Base URL
https://your-server:2088/api/v1

Requests and responses are JSON. Successful responses look like:

JSON
{ "status": "success", "data": { ... } }

Authentication

Every endpoint below (except /auth/login and /health) accepts either of two credentials:

MethodWho it's forHeader
API keyExternal systems (billing platforms, scripts) — this is what you'll useX-Api-Key: <key>
JWTThe dashboard, after a human logs in via POST /auth/loginAuthorization: Bearer <token>

An API key belongs to a specific admin or reseller account in the panel — the same way a WHM API token is tied to a specific root or reseller login. It is not a separate master credential: create (or designate) an admin or reseller user in ZWPanel for your billing integration, then use that account's key for every call. Whatever that account is allowed to do in the dashboard, the key can do via the API — including acting on behalf of its customers (see Acting on Behalf of a Customer).

The key is generated automatically when the account is created and returned once as api_key in the response (see Creating Accounts). You can also retrieve it later via GET /api/v1/users/:id as that admin/reseller.

cURL
curl https://your-server:2088/api/v1/health \
  -H "X-Api-Key: YOUR_API_KEY"
Keep this key secret. It can do everything the owning admin/reseller account can do in the dashboard. Rotate it by regenerating the owning account's key if it's ever exposed.

Creating Accounts

When a customer buys hosting, call this endpoint to create the hosting account. If a domain is supplied, ZWPanel runs full provisioning synchronously: OS user, home directory, PHP-FPM pool, Nginx vhost, DNS zone, and the plan's resource limits — the account is live when this call returns.

POST/api/v1/users
FieldTypeNotes
usernamestringRequired. 1–32 lowercase letters, digits, underscores.
passwordstringRequired. The account's dashboard login password.
emailstringRequired.
domainstringOptional. Omit to create a login with no hosting account yet; include to provision the full web account.
plan_idnumberRequired if domain is set — see Plans.
php_versionstringOptional, defaults to the server's default PHP version.
rolestringIgnored for reseller-held keys (always forced to client); honored for admin-held keys.
cURL
curl -X POST https://your-server:2088/api/v1/users \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "acmecorp",
    "password": "a-strong-generated-password",
    "email": "owner@acme.com",
    "domain": "acme.com",
    "plan_id": 1
  }'
201 Response
{
  "status": "success",
  "data": {
    "id": 42,
    "username": "acmecorp",
    "email": "owner@acme.com",
    "role": "client",
    "api_key": "f3a1...own-key-for-this-account",
    "plan_id": 1
  }
}

Other account-management endpoints: GET /api/v1/users (list), GET /api/v1/users/:id, PUT /api/v1/users/:id, DELETE /api/v1/users/:id.

Acting on Behalf of a Customer

Your billing system holds one key, tied to one admin or reseller account, and uses it to manage every customer it's responsible for — the same way a WHM API token manages every account on the server. Which customer a given call applies to is decided one of two ways, depending on the endpoint:

Domain-scoped (Email, DNS)

The target domain is already part of the URL, e.g. /api/v1/email/acme.com/mailboxes. ZWPanel resolves which account owns that domain and checks that your key's account is allowed to touch it: an admin key may touch any domain; a reseller key only domains belonging to its own customers.

Account-scoped (Databases, FTP, Files)

These endpoints don't take a domain in the path, so add ?username=<customer-username> to act on that customer's account. Omit it and the call defaults to the key's own account.

Your key's role?username= behavior
adminMay target any account on the node.
resellerMay target only accounts it created (its own customers).
clientIgnored — a client can only ever act on its own account.
cURL — reseller key acting for a customer
curl "https://your-server:2088/api/v1/account/databases?username=acmecorp" \
  -H "X-Api-Key: YOUR_API_KEY"
The File Manager endpoints (below) are the one exception: they're restricted to reseller and client keys. An admin-held key gets a 403 there, since the admin role has no hosting account of its own to browse.

Databases

MethodPathDescription
GET/account/databasesList databases.
POST/account/databasesCreate a database. Body: {"db_name": "myapp", "driver": "mysql"} (driver optional, mysql or postgres).
DELETE/account/databases/:idDrop a database.
PUT/account/databases/:id/passwordRotate the DB user's password. Body {"password": "..."} optional — omit to auto-generate.
GET/account/databases/:id/pma-ssoGet a phpMyAdmin link for the database.

All of the above accept ?username= as described in Acting on Behalf of a Customer.

cURL
curl -X POST "https://your-server:2088/api/v1/account/databases?username=acmecorp" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"db_name": "shop", "driver": "mysql"}'
201 Response
{
  "status": "success",
  "data": {
    "id": 7,
    "db_name": "acmecorp_shop",
    "db_user": "acmecorp_4f2a1c",
    "db_password": "generated-strong-password",
    "driver": "mysql"
  }
}

Email

MethodPathDescription
GET/email/domainsList domains with mail configured.
GET/email/:domain/mailboxesList mailboxes on a domain.
POST/email/:domain/mailboxesCreate a mailbox. Body: {"email": "sales@acme.com", "password": "..."} (min 8 chars).
PUT/email/:domain/mailboxes/:emailChange a mailbox password. Body: {"password": "..."}.
DELETE/email/:domain/mailboxes/:emailDelete a mailbox.
GET/email/:domain/forwardersList forwarders.
POST/email/:domain/forwardersCreate a forwarder. Body: {"source": "sales@acme.com", "destination": "me@gmail.com"}.
DELETE/email/:domain/forwarders/:sourceDelete a forwarder (URL-encode the source address).

:domain is domain-scoped, as described in Acting on Behalf of a Customer — no ?username= needed.

cURL
curl -X POST https://your-server:2088/api/v1/email/acme.com/mailboxes \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "sales@acme.com", "password": "a-strong-password"}'

FTP Accounts

MethodPathDescription
GET/account/ftp-accountsList FTP accounts.
POST/account/ftp-accountsCreate one. Body: {"ftp_username": "...", "password": "...", "directory": "/public_html"} — password and directory optional (password auto-generated, directory defaults to account root).
DELETE/account/ftp-accounts/:idDelete an FTP account.
PUT/account/ftp-accounts/:id/passwordRotate the password. Body {"password": "..."} optional.

All of the above accept ?username= as described in Acting on Behalf of a Customer. Note: ftp_username must be unique across the entire server, not just within one account.

File Manager

Restricted to reseller and client keys — an admin key gets 403 here (see above).
MethodPathDescription
GET/fs/list?dir=/public_htmlList a directory (relative to the account's home).
POST/fs/mkdirBody: {"dir": "...", "name": "..."}.
POST/fs/deleteBody: {"target": "/public_html/old.php"}.
GET/fs/read?target=...Read a text file's contents.
POST/fs/writeBody: {"target": "...", "content": "..."} — creates or overwrites.
POST/fs/uploadMultipart file upload.
POST/fs/compress / /fs/extractArchive a path to .tar.gz, or extract one.

All paths are resolved and sandboxed inside the target account's home directory — a path can't escape it even if crafted with ../. All of the above accept ?username= as described in Acting on Behalf of a Customer.

Errors

Errors share one shape:

JSON
{ "status": "error", "message": "Human-readable reason" }
StatusMeaning
400Invalid request body or parameters.
401Missing, invalid, or expired API key / JWT.
403Authenticated, but not allowed to act on this resource or role.
404Resource (or target account) not found.
409Conflict — e.g. a username, domain, or FTP login that's already taken.
500Server-side provisioning/OS failure. The message includes which step failed.

Other Endpoints

The full panel API covers everything the dashboard itself uses — DNS zone records, hosting plans, WordPress/Softaculous installs, PHP version management, resource metrics, backups, and more — authenticated the same way described above. The sections above cover what a billing integration typically needs; reach out if your integration needs something beyond this list and we'll document it here.