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:
https://your-server:2088/api/v1
Requests and responses are JSON. Successful responses look like:
{ "status": "success", "data": { ... } }
Authentication
Every endpoint below (except /auth/login and /health) accepts either of two credentials:
| Method | Who it's for | Header |
|---|---|---|
| API key | External systems (billing platforms, scripts) — this is what you'll use | X-Api-Key: <key> |
| JWT | The dashboard, after a human logs in via POST /auth/login | Authorization: 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 https://your-server:2088/api/v1/health \
-H "X-Api-Key: YOUR_API_KEY"
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.
/api/v1/users| Field | Type | Notes |
|---|---|---|
username | string | Required. 1–32 lowercase letters, digits, underscores. |
password | string | Required. The account's dashboard login password. |
email | string | Required. |
domain | string | Optional. Omit to create a login with no hosting account yet; include to provision the full web account. |
plan_id | number | Required if domain is set — see Plans. |
php_version | string | Optional, defaults to the server's default PHP version. |
role | string | Ignored for reseller-held keys (always forced to client); honored for admin-held keys. |
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
}'
{
"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 |
|---|---|
| admin | May target any account on the node. |
| reseller | May target only accounts it created (its own customers). |
| client | Ignored — a client can only ever act on its own account. |
curl "https://your-server:2088/api/v1/account/databases?username=acmecorp" \
-H "X-Api-Key: YOUR_API_KEY"
Databases
| Method | Path | Description |
|---|---|---|
| GET | /account/databases | List databases. |
| POST | /account/databases | Create a database. Body: {"db_name": "myapp", "driver": "mysql"} (driver optional, mysql or postgres). |
| DELETE | /account/databases/:id | Drop a database. |
| PUT | /account/databases/:id/password | Rotate the DB user's password. Body {"password": "..."} optional — omit to auto-generate. |
| GET | /account/databases/:id/pma-sso | Get a phpMyAdmin link for the database. |
All of the above accept ?username= as described in Acting on Behalf of a Customer.
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"}'
{
"status": "success",
"data": {
"id": 7,
"db_name": "acmecorp_shop",
"db_user": "acmecorp_4f2a1c",
"db_password": "generated-strong-password",
"driver": "mysql"
}
}
| Method | Path | Description |
|---|---|---|
| GET | /email/domains | List domains with mail configured. |
| GET | /email/:domain/mailboxes | List mailboxes on a domain. |
| POST | /email/:domain/mailboxes | Create a mailbox. Body: {"email": "sales@acme.com", "password": "..."} (min 8 chars). |
| PUT | /email/:domain/mailboxes/:email | Change a mailbox password. Body: {"password": "..."}. |
| DELETE | /email/:domain/mailboxes/:email | Delete a mailbox. |
| GET | /email/:domain/forwarders | List forwarders. |
| POST | /email/:domain/forwarders | Create a forwarder. Body: {"source": "sales@acme.com", "destination": "me@gmail.com"}. |
| DELETE | /email/:domain/forwarders/:source | Delete a forwarder (URL-encode the source address). |
:domain is domain-scoped, as described in Acting on Behalf of a Customer — no ?username= needed.
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
| Method | Path | Description |
|---|---|---|
| GET | /account/ftp-accounts | List FTP accounts. |
| POST | /account/ftp-accounts | Create one. Body: {"ftp_username": "...", "password": "...", "directory": "/public_html"} — password and directory optional (password auto-generated, directory defaults to account root). |
| DELETE | /account/ftp-accounts/:id | Delete an FTP account. |
| PUT | /account/ftp-accounts/:id/password | Rotate 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
| Method | Path | Description |
|---|---|---|
| GET | /fs/list?dir=/public_html | List a directory (relative to the account's home). |
| POST | /fs/mkdir | Body: {"dir": "...", "name": "..."}. |
| POST | /fs/delete | Body: {"target": "/public_html/old.php"}. |
| GET | /fs/read?target=... | Read a text file's contents. |
| POST | /fs/write | Body: {"target": "...", "content": "..."} — creates or overwrites. |
| POST | /fs/upload | Multipart file upload. |
| POST | /fs/compress / /fs/extract | Archive 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:
{ "status": "error", "message": "Human-readable reason" }
| Status | Meaning |
|---|---|
| 400 | Invalid request body or parameters. |
| 401 | Missing, invalid, or expired API key / JWT. |
| 403 | Authenticated, but not allowed to act on this resource or role. |
| 404 | Resource (or target account) not found. |
| 409 | Conflict — e.g. a username, domain, or FTP login that's already taken. |
| 500 | Server-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.