Table of contents

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

{ "data": [ { "id": 1, "name": "Starter", "slug": "starter", "disk_mb": 5120, "bandwidth_mb": 51200, "cpu_percent": 100, "ram_mb": 1024, "io_read_mbps": 50, "io_write_mbps": 50, "iops": 1024, "max_processes": 100, "max_domains": 5, "max_databases": 3, "max_apps": 3, "max_cron_jobs": 5, "terminal_access": true, "is_active": true } ] }

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

{ "data": { "id": 1, "name": "Starter", "disk_mb": 5120, ... } }

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

{ "current_page": 1, "data": [ { "id": 1, "username": "johndoe", "email": "john@example.com", "status": "active", "package": { "id": 1, "name": "Starter" }, "created_at": "2026-01-15T10:30:00.000000Z" } ], "per_page": 25, "total": 52, "last_page": 3 }
curl "https://panel.example.com/api/v1/accounts?username=johndoe&per_page=1" \ -H "Authorization: Bearer YOUR_TOKEN"

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.
curl "https://panel.example.com/api/v1/accounts/check-username?username=johndoe" \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "username": "johndoe", "available": true } }

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.

💡
WHMCS module note (2026-07-21)
If the product uses configurable options for IO / IOPS / process limits, pass them on create and on change-package/custom-limit updates via PATCH /accounts/{id}/resources . If the product only uses a MetroPanel package_id , no module change is required for IO — package values apply automatically. Module change is required only when you sell custom / override limits without a matching package field.

Example

curl -X POST https://panel.example.com/api/v1/accounts \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "johndoe", "name": "John Doe", "email": "john@example.com", "password": "SecurePass123!", "package_id": 1 }'

Example — package + limit overrides

curl -X POST https://panel.example.com/api/v1/accounts \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "johndoe", "name": "John Doe", "email": "john@example.com", "password": "SecurePass123!", "package_id": 1, "disk_limit_mb": 10240, "bandwidth_limit_mb": 102400, "cpu_limit_percent": 200, "ram_limit_mb": 2048, "io_read_mbps": 50, "io_write_mbps": 50, "iops": 1024, "max_processes": 100, "max_domains": 10, "max_databases": 10, "max_apps": 5, "terminal_access": true }'

Example — no package (WHMCS “None” / custom only)

curl -X POST https://panel.example.com/api/v1/accounts \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "johndoe", "name": "John Doe", "email": "john@example.com", "password": "SecurePass123!", "disk_limit_mb": 10240, "bandwidth_limit_mb": 102400, "cpu_limit_percent": 200, "ram_limit_mb": 2048, "io_write_mbps": 50, "iops": 1024, "max_processes": 100, "max_domains": 20, "max_databases": 15, "max_apps": 8, "terminal_access": false }'

Response (201)

{ "data": { "id": 5, "username": "johndoe", "email": "john@example.com", "status": "active", "linux_user": "johndoe", "home_path": "/home/johndoe", "package_id": 1, "disk_limit_mb": 10240, "bandwidth_limit_mb": 102400, "cpu_limit_percent": 200, "ram_limit_mb": 2048, "io_read_mbps": 50, "io_write_mbps": 50, "iops": 1024, "max_processes": 100, "max_domains": 10, "max_databases": 10, "max_apps": 5 } }

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

{ "data": { "id": 1, "username": "johndoe", "email": "john@example.com", "status": "active", "package": { "id": 1, "name": "Starter", "disk_mb": 5120 }, "domains": [ { "id": 1, "domain_name": "example.com", "is_primary": true } ], "apps_count": 2, "apps_limit": 5, "max_apps": 5 } }
curl "https://panel.example.com/api/v1/accounts/5?include=apps,usage" \ -H "Authorization: Bearer YOUR_TOKEN"

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

curl -X PATCH https://panel.example.com/api/v1/accounts/5/suspend \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "id": 5, "status": "suspended", ... } }

PATCH /accounts/{id}/unsuspend (Auth) — Unsuspend an account

Reactivates a suspended account.

curl -X PATCH https://panel.example.com/api/v1/accounts/5/unsuspend \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "id": 5, "status": "active", ... } }

PATCH /accounts/{id}/password (Auth) — Change account password

Request Body

Field Type Required Description
password string Required New password (min 8 chars)
curl -X PATCH https://panel.example.com/api/v1/accounts/5/password \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"password": "NewSecurePass456!"}'

PATCH /accounts/{id}/package (Auth) — Change package (upgrade/downgrade)

Request Body

Field Type Required Description
package_id int Required Target package ID
curl -X PATCH https://panel.example.com/api/v1/accounts/5/package \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"package_id": 2}'

Response

{ "data": { "id": 5, "username": "johndoe", "package": { "id": 2, "name": "Business", "disk_mb": 20480 }, ... } }
💡
Note
Downgrade fails if the account exceeds the new package limits (domains, databases, apps, disk). Changing package resets this account’s limit fields (disk/bw/CPU/RAM + IO/IOPS/max_processes + domains/databases/apps + terminal_access ) from the target package.

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
curl -X PATCH https://panel.example.com/api/v1/accounts/5/resources \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "disk_limit_mb": 20480, "bandwidth_limit_mb": 204800, "cpu_limit_percent": 200, "ram_limit_mb": 4096, "io_read_mbps": 50, "io_write_mbps": 50, "iops": 1024, "max_processes": 100, "max_domains": 25, "max_databases": 20, "max_apps": 10, "terminal_access": true }'
💡
Changing package
via POST /accounts/{id}/package resets disk/bw/CPU/RAM, IO/IOPS/max_processes , domains/databases/apps, and terminal_access from the target package (same as the panel).

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.

curl -s https://panel.example.com/api/v1/accounts/1/usage \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "disk_used_mb": 718, "disk_limit_mb": 5120, "bandwidth_used_mb": 1250, "bandwidth_limit_mb": 51200, "cpu_used_percent": 12, "cpu_limit_percent": 100, "ram_used_mb": 256, "ram_limit_mb": 1024 } }

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.

💡
Security / load
Only accounts visible to the token user are returned. Unknown or out-of-scope IDs appear in meta.missing_ids (no existence leak). Max 100 IDs per request. Snapshots use a ~60s cache shared with the single-account usage endpoint.

Query Parameters

Parameter Type Required Description
hosting_account_id[] int[] Required 1–100 account IDs. Duplicates ignored.
curl -g "https://panel.example.com/api/v1/accounts/usage/bulk?hosting_account_id[]=1&hosting_account_id[]=55" \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "1": { "disk_used_mb": 512, "disk_limit_mb": 5120, "bandwidth_used_mb": 1024, "bandwidth_limit_mb": 51200, "cpu_used_percent": 12, "cpu_limit_percent": 100, "ram_used_mb": 256, "ram_limit_mb": 1024 }, "55": { "...": "..." } }, "meta": { "requested": 2, "returned": 2, "missing_ids": [] } }

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
curl -X POST https://panel.example.com/api/v1/accounts/5/login-link \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target":"apps"}'
curl -X POST https://panel.example.com/api/v1/accounts/5/login-link \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target":"app","app_id":8}'

Response

{ "data": { "login_url": "https://panel.example.com/api/v1/sso/login/42?expires=1751569200⌖=apps&signature=abc123...", "expires_in": 300, "user_username": "johndoe", "user_email": "john@example.com", "target": "apps" } }

Security: target / app_id / domain_id are part of the HMAC signature — tampering yields 403. Unknown target422. Suspended account / terminal disabled / foreign app_id403 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.

curl -X DELETE https://panel.example.com/api/v1/accounts/5 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "message": "Termination queued" }

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
💡
FQDN rules
Must match (label.)+tld , lowercase. Rejected: whitespace, / ; \ , .. , single-label names. Invalid names return 422 .
curl -X POST https://panel.example.com/api/v1/domains \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hosting_account_id": 5, "domain_name": "example.com"}'

Response (201)

{ "data": { "id": 12, "domain_name": "example.com", "hosting_account_id": 5, ... } }

A PHP nginx vhost is provisioned automatically for standalone domains.

GET /domains/{id} (Auth) — Show domain details

curl https://panel.example.com/api/v1/domains/12 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "id": 12, "domain_name": "example.com", "hosting_account": { ... } } }

POST /domains/{id}/ssl (Auth) — Install SSL certificate

Request Body

Field Type Required Description
email string Optional Let's Encrypt notification email
curl -X POST https://panel.example.com/api/v1/domains/12/ssl \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email": "admin@example.com"}'

DELETE /domains/{id} (Auth) — Remove a domain

curl -X DELETE https://panel.example.com/api/v1/domains/12 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "message": "Domain removal queued" }

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.

curl https://panel.example.com/api/v1/app-templates \ -H "Authorization: Bearer YOUR_TOKEN"
{ "data": [ { "key": "wordpress", "name": "WordPress", "description": "Popular CMS for blogs and websites.", "icon": "wordpress", "requires_domain": true, "env_schema": [] }, { "key": "nodejs", "name": "Node.js App", "description": "Node.js applications…", "icon": "nodejs", "requires_domain": false, "env_schema": [], "node_versions": ["18", "20", "22"] } ] }

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

curl "https://panel.example.com/api/v1/apps?hosting_account_id=5&per_page=25" \ -H "Authorization: Bearer YOUR_TOKEN"
{ "current_page": 1, "data": [ { "id": 8, "hosting_account_id": 5, "name": "My Blog", "template_key": "wordpress", "template_name": "WordPress", "status": "running", "domain": { "id": 12, "domain_name": "blog.example.com" }, "created_at": "2026-07-10T12:00:00+00:00", "updated_at": "2026-07-10T12:05:00+00:00" } ], "per_page": 25, "total": 1 }

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.

curl -X POST https://panel.example.com/api/v1/apps \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hosting_account_id": 5, "template_key": "wordpress", "name": "My Blog", "domain_id": 12}'

Response (201)

{ "data": { "id": 8, "name": "My Blog", "template_key": "wordpress", "status": "installing", ... } }

GET /apps/{id} (Auth) — Show app details

curl https://panel.example.com/api/v1/apps/8 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

Requires ability apps:read and InstalledAppPolicy (tenant-scoped). Secret env values are redacted.

{ "data": { "id": 8, "name": "My Blog", "status": "running", "domain": { ... }, "env_vars": [ { "id": 1, "key": "APP_URL", "value": "https://blog.example.com", "is_secret": false }, { "id": 2, "key": "DB_PASSWORD", "value": null, "is_secret": true } ] } }

DELETE /apps/{id} (Auth) — Remove an installed app

curl -X DELETE https://panel.example.com/api/v1/apps/8 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "message": "App removed" }

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.

curl -X POST https://panel.example.com/api/v1/apps/8/deploy-zip \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "archive=@/path/to/site.zip"

Response

{ "message": "ZIP deploy started…", "data": { "app": { … }, "archive_bytes": 12345 } }

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).

curl -X POST https://panel.example.com/api/v1/apps/8/deploy-git \ -H "Authorization: Bearer YOUR_TOKEN"

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

curl "https://panel.example.com/api/v1/databases?hosting_account_id=5" \ -H "Authorization: Bearer YOUR_TOKEN"

GET /databases/{id} (Auth) — Show database (password hidden)

curl https://panel.example.com/api/v1/databases/3 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "id": 3, "hosting_account_id": 5, "name": "My App DB", "db_name": "user_myapp", "db_user": "user_myapp", "db_host": "127.0.0.1", "status": "active", "allowed_ips": null, "hosting_account": { ... } } }
💡
Security
db_password is hidden from all API responses (list, show, create, reset).

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.

curl -X POST https://panel.example.com/api/v1/databases \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hosting_account_id": 5, "name": "My App"}'

Response (201)

{ "data": { "id": 3, "db_name": "user_myapp", "db_user": "user_myapp", "db_host": "127.0.0.1", "status": "pending", ... } }

PATCH /databases/{id}/password (Auth) — Reset database password

curl -X PATCH https://panel.example.com/api/v1/databases/3/password \ -H "Authorization: Bearer YOUR_TOKEN"

Resets the MySQL password on the server. The new password is not returned in the API response.

Response

{ "data": { "id": 3, "db_name": "user_myapp", "db_user": "user_myapp", "status": "active", ... } }

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
curl -X POST https://panel.example.com/api/v1/databases/3/allowed-ips \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"ip": "203.0.113.10"}'

DELETE /databases/{id}/allowed-ips/{ip} (Auth) — Revoke remote MySQL access for IP

curl -X DELETE https://panel.example.com/api/v1/databases/3/allowed-ips/203.0.113.10 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "id": 3, "allowed_ips": null, ... } }

DELETE /databases/{id} (Auth) — Remove a database

Response

{ "message": "Database removal queued" }

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)
curl "https://panel.example.com/api/v1/cron-jobs?hosting_account_id=5" \ -H "Authorization: Bearer YOUR_TOKEN"

GET /cron-jobs/{id} (Auth) — Show cron job details

curl https://panel.example.com/api/v1/cron-jobs/4 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "data": { "id": 4, "hosting_account_id": 5, "name": "WP Cron", "schedule": "0 * * * *", "command": "php /home/user/app/artisan schedule:run", "enabled": true, "hosting_account": { ... } } }

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.

curl -X POST https://panel.example.com/api/v1/cron-jobs \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"hosting_account_id": 5, "schedule": "0 * * * *", "command": "php /home/user/cron.php"}'

Response (201)

{ "data": { "id": 4, "name": "Cron Job 1", "enabled": true, ... } }

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
curl -X PATCH https://panel.example.com/api/v1/cron-jobs/4 \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"schedule": "*/15 * * * *", "command": "php /home/user/cron.php", "enabled": false}'

DELETE /cron-jobs/{id} (Auth) — Remove a cron job

curl -X DELETE https://panel.example.com/api/v1/cron-jobs/4 \ -H "Authorization: Bearer YOUR_TOKEN"

Response

{ "message": "Cron job removed" }

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:

  1. Call POST /accounts/{id}/login-link (optionally with target) to get a signed URL
  2. Redirect the customer's browser to the returned login_url
  3. 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.