API Reference
Resource endpoints for MetroPanel. For auth, tokens, rate limits, and errors, see API.
Requires ability packages:read. Returns packages available to the token owner (admin: all active; reseller: own + assigned reseller packages).
GET /packages (Auth) — List available hosting packages
Returns all active packages visible to the authenticated user.
Response
IO / processes on packages: io_read_mbps, io_write_mbps, iops, and max_processes may be null (unlimited). Prefer reading them from GET /packages when provisioning from a package; pass the same keys on POST /accounts only when overriding or using custom (no package) limits.
GET /packages/{id} (Auth) — Show a single package
Requires ability packages:read and policy authorization (view). Unauthorized packages return 403.
Response
Panel login (end users): Each hosting account gets its own panel user. Login identifier is the hosting username (must be unique across hosting accounts and panel users). Contact email is required for notices / WHMCS client data and may be reused across accounts (same billing client, multiple services). Admin/reseller panel logins still use unique emails. If the customer has TOTP 2FA enabled, password login challenges that specific account user; SSO login-link still signs them in without a TOTP prompt.
GET /accounts (Auth) — List hosting accounts
Query Parameters
| Parameter | Type | Description |
username
|
string | Optional. Exact match (case-insensitive, normalised to lowercase) on hosting account username. When set, returns 0 or 1 row (usernames are unique). Scoped to accounts visible to the token. |
per_page
|
int | Results per page (default: 25, max: 100) |
page
|
int | Page number |
Response
GET /accounts/check-username (Auth) — Check username availability
Lightweight check before POST /accounts (manual WHMCS usernames). Same validation rules as create. Returns only available: true|false — does not reveal which table holds a conflict.
Query Parameters
| Parameter | Type | Required | Description |
username
|
string | Required |
3–16 chars, a-z / 0-9 / - / _. Normalised to lowercase.
|
Response
Invalid format → 422. Ability: accounts:read.
POST /accounts (Auth) — Create a hosting account
Request Body
| Field | Type | Required | Description |
username
|
string | Required |
3–16 chars, alphanumeric + dash/underscore. Unique on hosting_accounts and users (becomes the end-user panel login)
|
name
|
string | Required | Display name (max 100) |
email
|
string | Required | Contact email — not unique. Same address may be used on multiple accounts (WHMCS multi-service clients). Must not already belong to an admin/reseller panel login |
password
|
string | Required | Min 8 characters (Linux + panel login password) |
package_id
|
int | Optional |
Package ID from GET /packages. Omit or send null for panel-style None — custom resource limits (then all limit fields below are required). When set, limits are optional overrides
|
disk_limit_mb
|
int | Conditional |
Disk quota in MB (min 100). Required when package_id is omitted; otherwise defaults to package disk_mb
|
bandwidth_limit_mb
|
int | Conditional | Monthly bandwidth in MB (min 100). Required without package; else package default |
cpu_limit_percent
|
int | Conditional | CPU percent (1–10000). Required without package; else package default |
ram_limit_mb
|
int | Conditional | RAM in MB (min 128). Required without package; else package default |
io_read_mbps
|
int|null | Optional |
Disk IO read cap (MB/s). Min 5 if set. null / omit = unlimited (or package default when package_id is set). Enforced only on hosts that support cgroup IO
|
io_write_mbps
|
int|null | Optional |
Disk IO write cap (MB/s). Min 5 if set. null / omit = unlimited (or package default)
|
iops
|
int|null | Optional |
IOPS cap. Min 100 if set. null / omit = unlimited (or package default)
|
max_processes
|
int|null | Optional |
Max processes (pids). Min 20 if set. null / omit = unlimited (or package default). Too low can break PHP-FPM / cron / Docker apps
|
max_domains
|
int | Conditional |
Max custom domains (min 1). Required without package; else package max_domains
|
max_databases
|
int | Conditional | Max MySQL databases (min 0). Required without package; else package default |
max_apps
|
int | Conditional | Max installed apps (min 0). Required without package; else package default |
terminal_access
|
bool | Optional | Browser terminal / app shell for this account only. Defaults to the package flag when omitted. Enabling here does not enable terminal for sibling accounts on the same package |
WHMCS / billing: Send the service username as username and the client contact email as email. A second order for the same client email will succeed as long as the username is new. Tell customers to sign in with their hosting username, not email, when they have more than one account.
Custom resource / feature limits: Same as the panel create form.
• With package: send package_id; optionally override disk/bw/CPU/RAM, IO read/write, IOPS, max processes, domains/databases/apps, and/or terminal_access.
• No package (“None”): omit package_id and send the seven required numeric limits (disk/bw/CPU/RAM + domains/databases/apps). Optionally send io_read_mbps, io_write_mbps, iops, max_processes (omit or null = unlimited). MetroPanel attaches the internal custom package when present (else the lowest-disk package available) for FTP and other package-only flags. Send terminal_access explicitly if you want terminal on (otherwise it follows that package’s default, usually off).
Reseller tokens still pass reseller pool checks against the effective resource limits (disk/CPU/RAM/bw, plus IO read/write, IOPS, and max processes when those reseller pools are set). IO / IOPS / processes are always allocation-sum gated (cannot oversell); disk/CPU/RAM/bw follow the reseller’s overselling setting.
Example
Example — package + limit overrides
Example — no package (WHMCS “None” / custom only)
Response (201)
GET /accounts/{id} (Auth) — Show account details
Includes package + domains. Always returns apps_count and apps_limit (effective max apps). Optional sparse embeds via ?include=.
Query Parameters
| Parameter | Type | Description |
include
|
string |
Comma-separated: apps (up to 10 summary items, each includes template_name), usage (same cached snapshot as GET …/usage)
|
Response
PATCH /accounts/{id}/suspend (Auth) — Suspend an account
Suspends the hosting account. The user will not be able to access their sites or panel.
Example
Response
PATCH /accounts/{id}/unsuspend (Auth) — Unsuspend an account
Reactivates a suspended account.
Response
PATCH /accounts/{id}/password (Auth) — Change account password
Request Body
| Field | Type | Required | Description |
password
|
string | Required | New password (min 8 chars) |
PATCH /accounts/{id}/package (Auth) — Change package (upgrade/downgrade)
Request Body
| Field | Type | Required | Description |
package_id
|
int | Required | Target package ID |
Response
PATCH /accounts/{id}/resources (Auth) — Update resource / feature limits
Same fields as the panel Account → Resource Limits form. Does not change package_id. Cannot lower domains/databases/apps below current usage.
Request Body
| Field | Type | Required | Description |
disk_limit_mb
|
int | Required | Min 100 |
bandwidth_limit_mb
|
int | Required | Min 100 |
cpu_limit_percent
|
int | Required | 1–10000 |
ram_limit_mb
|
int | Required | Min 128 |
io_read_mbps
|
int|null | Optional |
Min 5 if set. Omit = leave unchanged. null = unlimited
|
io_write_mbps
|
int|null | Optional |
Min 5 if set. Omit = leave unchanged. null = unlimited
|
iops
|
int|null | Optional |
Min 100 if set. Omit = leave unchanged. null = unlimited
|
max_processes
|
int|null | Optional |
Min 20 if set. Omit = leave unchanged. null = unlimited
|
max_domains
|
int | Required | Min 1 |
max_databases
|
int | Required | Min 0 |
max_apps
|
int | Required | Min 0 |
terminal_access
|
bool | Required | Per-account terminal; does not change the package or siblings |
GET /accounts/{id}/usage (Auth) — Get resource usage stats
Returns current disk, bandwidth, CPU, and RAM usage with limits. Responses are cached ~60 seconds per account (same cache as bulk) to limit host du / cgroup probes.
Response
GET /accounts/usage/bulk (Auth) — Bulk resource usage (WHMCS cron)
Returns usage snapshots for many hosting account IDs in one request. Prefer this for WHMCS UsageUpdate instead of N× GET /accounts/{id}/usage.
Query Parameters
| Parameter | Type | Required | Description |
hosting_account_id[]
|
int[] | Required | 1–100 account IDs. Duplicates ignored. |
Response
Ability: accounts:read. Route is registered before /accounts/{id} so usage is not treated as an account id.
POST /accounts/{id}/login-link (Auth) — Generate SSO login URL
Generates a short-lived signed URL that logs the customer into the panel. Valid for 5 minutes. Ensures an end-user panel login exists. Optional JSON body deep-links into a whitelisted panel section (WHMCS Client Area Quick Shortcuts).
Request Body (all optional)
| Field | Type | Description |
target
|
string |
Whitelist: dashboard (default), apps, domains, databases, cron, files, terminal, backups, app. Empty body = dashboard (backward compatible)
|
app_id
|
int |
Required when target=app. Must belong to this account
|
domain_id
|
int |
Optional; with target=domains opens that domain’s page
|
Response
Security: target / app_id / domain_id are part of the HMAC signature — tampering yields 403. Unknown target → 422. Suspended account / terminal disabled / foreign app_id → 403 or 404. Ability accounts:write. SSO redirect sets Referrer-Policy: no-referrer. File manager / terminal / backups stay panel-only; WHMCS only deep-links via these targets.
DELETE /accounts/{id} (Auth) — Terminate (delete) an account
Queues account termination. Removes the Linux user, home directory, databases, and all associated data. This is irreversible.
Response
Requires domains:read / domains:write. Domain names are validated as FQDNs before nginx provisioning (rejects injection characters, path segments, and single-label hostnames).
GET /domains (Auth) — List domains
Query Parameters
| Parameter | Type | Description |
hosting_account_id
|
int | Filter by account (optional) |
per_page
|
int | Results per page (default: 25) |
POST /domains (Auth) — Add domain to account
Request Body
| Field | Type | Required | Description |
hosting_account_id
|
int | Required | Account to attach domain to |
domain_name
|
string | Required |
Valid FQDN, e.g. example.com (max 253)
|
is_primary
|
bool | Optional | Set as primary domain |
Response (201)
A PHP nginx vhost is provisioned automatically for standalone domains.
GET /domains/{id} (Auth) — Show domain details
Response
POST /domains/{id}/ssl (Auth) — Install SSL certificate
Request Body
| Field | Type | Required | Description |
email
|
string | Optional | Let's Encrypt notification email |
DELETE /domains/{id} (Auth) — Remove a domain
Response
Requires apps:read / apps:write. All app endpoints use InstalledAppPolicy and are scoped to accounts visible to the token owner. Optional domain_id on create must belong to the same hosting account.
GET /app-templates (Auth) — List installable app templates
Discoverable catalog for WHMCS / billing “Install app” UIs. Ability apps:read.
Use key as template_key on POST /apps. requires_domain is derived from the template runtime (PHP/static default true) unless the template overrides it.
GET /apps (Auth) — List installed apps
Paginated summary shape for WHMCS Client Area. Use GET /apps/{id} for full detail + env vars.
Query Parameters
| Parameter | Type | Description |
hosting_account_id
|
int | Filter by account (recommended for WHMCS; must be visible) |
per_page
|
int | Default 25, max 100 |
status
|
string |
Optional filter (e.g. running)
|
status values
pending, installing, building, updating, starting, stopping, running, stopped, failed, removing
template_name comes from the same catalog as GET /app-templates (falls back to template_key if unknown). When present, WHMCS Client Area can skip an extra templates call.
POST /apps (Auth) — Install an app
Request Body
| Field | Type | Required | Description |
hosting_account_id
|
int | Required | Account to install on |
template_key
|
string | Required | App template (e.g. "wordpress", "nodejs") |
name
|
string | Required | App name (max 64) |
domain_id
|
int | Optional | Domain to attach app to |
node_version
|
string | Optional | "18", "20", "22" |
start_command
|
string | Optional | Custom start command |
env
|
object | Optional | Environment variables (secret values are redacted on read) |
Installation is queued (InstallAppJob). Poll GET /apps/{id} for status. Secret env vars appear as "value": null, "is_secret": true in API responses.
Response (201)
GET /apps/{id} (Auth) — Show app details
Response
Requires ability apps:read and InstalledAppPolicy (tenant-scoped). Secret env values are redacted.
DELETE /apps/{id} (Auth) — Remove an installed app
Response
POST /apps/{id}/deploy-zip (Auth) — Deploy a ZIP into PHP / Node / Static app
Hostinger-style “put online”: multipart upload replaces the app deploy root (public_html, public, or Node app root). Allowed templates: php, nodejs, static. Not File Manager — no arbitrary paths. Node builds run on the server after extract. Poll GET /apps/{id} for status.
Response
POST /apps/{id}/deploy-git (Auth) — Git pull + rebuild (configured Node apps)
Requires the app to already have Git deploy configured in the panel (repository + deploy key / HTTPS).
Manage MySQL databases for hosting accounts. Subject to package max_databases limit.
GET /databases (Auth) — List MySQL databases
Query Parameters
| Parameter | Type | Description |
hosting_account_id
|
int | Filter by account (optional) |
per_page
|
int | Pagination size (default 25) |
db_password is never returned by the API (model $hidden). Use the panel UI or phpMyAdmin for credential recovery workflows.
Example
GET /databases/{id} (Auth) — Show database (password hidden)
Response
POST /databases (Auth) — Create a database
Request Body
| Field | Type | Required | Description |
hosting_account_id
|
int | Required | Account to create database on |
name
|
string | Optional | Display label (auto-generated if omitted) |
MySQL provisioning runs asynchronously via queue. The JSON response does not include db_password.
Response (201)
PATCH /databases/{id}/password (Auth) — Reset database password
Resets the MySQL password on the server. The new password is not returned in the API response.
Response
POST /databases/{id}/allowed-ips (Auth) — Allow remote MySQL access from IP
Request Body
| Field | Type | Required | Description |
ip
|
string | Required | IPv4 or IPv6 address |
DELETE /databases/{id}/allowed-ips/{ip} (Auth) — Revoke remote MySQL access for IP
Response
DELETE /databases/{id} (Auth) — Remove a database
Response
Actual MySQL drop runs asynchronously via queue.
Manage per-account crontab entries. Subject to package max_cron_jobs limit. Commands are hardened: shell metacharacters ; | &, backticks, $(), sudo/su, and redirects into system paths are rejected (HTTP 422).
GET /cron-jobs (Auth) — List cron jobs
Query Parameters
| Parameter | Type | Description |
hosting_account_id
|
int | Filter by account (optional) |
per_page
|
int | Pagination size (default 25) |
GET /cron-jobs/{id} (Auth) — Show cron job details
Response
POST /cron-jobs (Auth) — Create a cron job
Request Body
| Field | Type | Required | Description |
hosting_account_id
|
int | Required | Account to attach cron job to |
name
|
string | Optional | Display name |
schedule
|
string | Required |
Standard cron expression (e.g. 0 * * * *)
|
command
|
string | Required | Shell command (max 2000 chars) |
Crontab is synced immediately on create/update/delete.
Response (201)
PATCH /cron-jobs/{id} (Auth) — Update a cron job
Request Body
| Field | Type | Required | Description |
name
|
string | Optional | Display name |
schedule
|
string | Required | Cron expression |
command
|
string | Required | Shell command |
enabled
|
bool | Optional | Enable or disable the job |
DELETE /cron-jobs/{id} (Auth) — Remove a cron job
Response
The following are available in the MetroPanel UI but intentionally not exposed via REST API (same as most hosting panels):
- Account backups — create, restore, download
- Per-app backups
- Panel-level backups
- FTP account management
- File manager
- Web terminal
- phpMyAdmin launch
GET /sso/login/{user_id}?expires=...&signature=... (Signed URL) — Auto-login via signed URL
This endpoint is not called directly. Instead:
-
Call
POST /accounts/{id}/login-link(optionally withtarget) to get a signed URL -
Redirect the customer's browser to the returned
login_url -
They are logged in and redirected to the mapped panel page (
dashboard,/apps,/domains, …)
Security: URLs are cryptographically signed (covers user, expires, target, and optional app_id/domain_id) and expire after 5 minutes. Tampering with target fails signature validation (403). Throttle: 30/min. Redirect sets Referrer-Policy: no-referrer.
Back to API for authentication and overview. WHMCS setup: WHMCS Module.