OpenPanel API
OpenPanel exposes a full REST API that mirrors all web UI functionality. Every feature available in the panel can be accessed programmatically.
A machine-readable OpenAPI 3.0 spec covering every endpoint below is also available — import it into Postman/Insomnia, generate a client SDK, or load it into Swagger UI/Redoc.
Authentication​
All API requests require a Bearer token obtained via the login endpoint.
Step 1 — Get a token:
curl -s -X POST https://panel.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username": "stefan", "password": "yourpassword"}'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 86400
}
Step 2 — Use it on every request:
curl https://panel.example.com/api/account \
-H "Authorization: Bearer eyJhbGciOi..."
Tokens expire after 24 hours. Both the blanket api feature flag AND the specific feature flag must be enabled in the user's plan.
MySQL​
All MySQL endpoints are flat — there is no /api/mysql/<db_name>/users nesting. Databases live under /api/mysql/databases, users under /api/mysql/users, and the two are tied together with a separate /api/mysql/grants resource.
List databases​
GET /api/mysql/databases
Authorization: Bearer <token>
{
"databases": [
{"name": "mydb", "assigned_users": "myuser"}
],
"total": 1
}
Create a database​
POST /api/mysql/databases
Content-Type: application/json
{"name": "newdb"}
Drop a database​
DELETE /api/mysql/databases/newdb
List tables in a database​
GET /api/mysql/databases/mydb/tables
{
"database": "mydb",
"tables": [
{"table": "wp_posts", "rows": 120, "data_length": 98304, "index_length": 32768, "data_free": 0}
]
}
Optimize / repair a database​
Runs OPTIMIZE TABLE or REPAIR TABLE against every table in the database.
POST /api/mysql/databases/mydb/optimize
POST /api/mysql/databases/mydb/repair
{
"database": "mydb",
"action": "optimize",
"results": [
{"table": "wp_posts", "status": "ok", "details": [{"op": "optimize", "msg_type": "status", "msg_text": "OK"}]}
]
}
Export a database​
Streams a .sql or .sql.gz file download.
GET /api/mysql/databases/mydb/export?format=sql
Import a database​
Multipart upload — field name must be file. Accepts .sql or .sql.gz.
POST /api/mysql/databases/mydb/import
Content-Type: multipart/form-data
Get per-database disk usage​
Mirrors the web UI's database-size view — distinct from /api/mysql/info, which has no size data, and /api/mysql/databases/<db>/tables, which is per-table for a single database.
GET /api/mysql/size?unit=mb&show_all=false
{"sizes": [{"database": "mydb", "size": 12.4}], "total": 1, "unit": "MB"}
Set the MySQL root password​
Write-only — the current password can't be retrieved. Updates both the % and localhost hosts and restarts the service.
PUT /api/mysql/root-password
Content-Type: application/json
{"password": "newRootPass123"}
List database users​
GET /api/mysql/users
{"users": ["myuser", "shopuser"], "total": 2}
Create a database user​
POST /api/mysql/users
Content-Type: application/json
{"username": "dbuser", "password": "securepass", "host": "%"}
host defaults to % if omitted.
Delete a database user​
DELETE /api/mysql/users/dbuser?host=%
Change a database user's password​
PATCH /api/mysql/users/dbuser/password
Content-Type: application/json
{"password": "newpass", "host": "%"}
Get a user's privileges on a database​
GET /api/mysql/users/dbuser/privileges/mydb?host=%
{"username": "dbuser", "host": "%", "database": "mydb", "privileges": ["ALL PRIVILEGES"]}
Grant privileges​
POST /api/mysql/grants
Content-Type: application/json
{"username": "dbuser", "database": "mydb", "privileges": ["ALL PRIVILEGES"], "host": "%"}
Revoke privileges​
DELETE /api/mysql/grants
Content-Type: application/json
{"username": "dbuser", "database": "mydb", "host": "%"}
Get databases/users/assignments summary​
GET /api/mysql/info
{
"databases": ["mydb", "shop"],
"users": ["dbuser", "shopuser"],
"assigned_databases": [{"database": "mydb", "users": "dbuser"}]
}
Show full processlist​
GET /api/mysql/processlist
{
"processlist": [
{"id": 42, "user": "dbuser", "host": "localhost", "db": "mydb", "command": "Query", "time": 0, "state": "", "info": "SELECT 1"}
],
"total": 1
}
Remote access status​
GET /api/mysql/remote-access
{
"enabled": false,
"server_ip": "203.0.113.10",
"port": "3306",
"user_access": [{"username": "dbuser", "hosts": ["%"]}]
}
Enable / disable remote access​
POST /api/mysql/remote-access
Content-Type: application/json
{"action": "enable"}
Grant a remote-access entry​
Creates a new user@host (cloning grants from an existing host for that user, if any).
POST /api/mysql/remote-access/entries
Content-Type: application/json
{"username": "dbuser", "host": "1.2.3.4", "password": "securepass"}
Change a remote-access entry's host​
PATCH /api/mysql/remote-access/entries
Content-Type: application/json
{"username": "dbuser", "old_host": "1.2.3.4", "new_host": "5.6.7.8"}
Remove a remote-access entry​
DELETE /api/mysql/remote-access/entries
Content-Type: application/json
{"username": "dbuser", "host": "1.2.3.4"}
Get server configuration​
GET /api/mysql/configuration
{
"configuration": {"max_connections": "150"},
"available_keys": ["max_connections", "max_allowed_packet", "innodb_buffer_pool_size", "..."]
}
Update server configuration​
Only keys present in available_keys are accepted. Restarts MySQL/MariaDB.
PUT /api/mysql/configuration
Content-Type: application/json
{"max_connections": "200"}
{"updated": true, "restarted": true, "configuration": {"max_connections": "200"}}
PostgreSQL​
Same flat structure as MySQL: databases, users, and grants are separate top-level resources.
List databases​
GET /api/postgresql/databases
{
"databases": [{"name": "pgdb", "assigned_users": "pguser"}],
"total": 1
}
Create a database​
POST /api/postgresql/databases
Content-Type: application/json
{"name": "pgdb"}
Drop a database​
DELETE /api/postgresql/databases/pgdb
Export a database​
Streams a .sql dump.
GET /api/postgresql/databases/pgdb/export
Import a database​
Multipart upload — field name must be file.
POST /api/postgresql/databases/pgdb/import
Content-Type: multipart/form-data
{"database": "pgdb", "file": "dump.sql", "imported": true}
List users​
GET /api/postgresql/users
{"users": ["pguser"], "total": 1}
Create a user​
POST /api/postgresql/users
Content-Type: application/json
{"username": "pguser", "password": "securepass"}
Drop a user​
Revokes access from every database before dropping the role.
DELETE /api/postgresql/users/pguser
Change a user's password​
PATCH /api/postgresql/users/pguser/password
Content-Type: application/json
{"password": "newpass"}
Grant all privileges on a database​
POST /api/postgresql/grants
Content-Type: application/json
{"username": "pguser", "database": "pgdb"}
Revoke privileges​
DELETE /api/postgresql/grants
Content-Type: application/json
{"username": "pguser", "database": "pgdb"}
Get databases/users/assignments summary​
GET /api/postgresql/info
Show active connections​
GET /api/postgresql/processlist
{
"processlist": [
{"pid": 123, "user": "pguser", "application": "psql", "client_addr": "127.0.0.1",
"state": "active", "wait_event_type": "", "wait_event": "", "query": "SELECT 1",
"backend_type": "client backend", "duration": "0:00:00.001"}
],
"total": 1
}
Remote access status​
GET /api/postgresql/remote-access
{"enabled": false, "server_ip": "203.0.113.10", "port": "5432", "postgres_port": 5432}
Enable / disable remote access​
POST /api/postgresql/remote-access
Content-Type: application/json
{"action": "enable"}
Get / update server configuration​
Same shape as MySQL configuration — restarts PostgreSQL on save.
GET /api/postgresql/configuration
PUT /api/postgresql/configuration
Content-Type: application/json
{"max_connections": "200"}
Domains​
List all domains​
GET /api/domains
{
"domains": [
{
"domain": "example.com", "docroot": "/var/www/html", "php_version": "8.2",
"site_count": 1, "redirect_url": null, "ssl": true, "status": "active",
"suspend_comment": null, "has_dns_zone": true
}
],
"total": 1
}
Add a domain​
docroot defaults to /var/www/html/. .onion domains also require hs_ed25519_public_key and hs_ed25519_secret_key.
POST /api/domains
Content-Type: application/json
{"domain": "newdomain.com"}
Remove a domain​
DELETE /api/domains/olddomain.com
Get domain status​
SSL / suspend / redirect status in one call.
GET /api/domains/example.com/status
{"domain": "example.com", "ssl": true, "status": "active", "suspend_comment": null, "redirect_url": null}
Suspend / unsuspend a domain​
POST /api/domains/example.com/suspend
POST /api/domains/example.com/unsuspend
Get / change the document root​
docroot must resolve inside /var/www/html/.
GET /api/domains/example.com/docroot
PUT /api/domains/example.com/docroot
Content-Type: application/json
{"docroot": "/var/www/html/example.com/public"}
Get / set / remove a redirect​
GET /api/domains/example.com/redirect
PUT /api/domains/example.com/redirect
Content-Type: application/json
{"redirect_url": "https://newsite.com"}
DELETE /api/domains/example.com/redirect
Get SSL status​
GET /api/domains/example.com/ssl
{"domain": "example.com", "ssl_mode": "auto", "keys": ""}
Issue / renew SSL​
action is required — one of autossl, generate, switch_and_generate, or custom (which also requires public_path and private_path, both inside /var/www/html/).
POST /api/domains/example.com/ssl
Content-Type: application/json
{"action": "autossl"}
Custom certificate:
POST /api/domains/example.com/ssl
Content-Type: application/json
{
"action": "custom",
"public_path": "/var/www/html/certs/example.com.crt",
"private_path": "/var/www/html/certs/example.com.key"
}
Get / save the VHost config​
GET /api/domains/example.com/vhost
PUT /api/domains/example.com/vhost
Content-Type: application/json
{"vhost": "...vhost content..."}
Get / set a display-case override​
Cosmetic only — e.g. showing "MyBrand.com" instead of "mybrand.com" in the UI.
GET /api/domains/example.com/capitalize
{"domain": "example.com", "capitalized_domain": "example.com"}
PUT /api/domains/example.com/capitalize
Content-Type: application/json
{"capitalized_domain": "Example.com"}
Get the TLSA record hash​
For DANE/TLSA DNS records, computed from the domain's active SSL certificate (Let's Encrypt or custom).
GET /api/domains/example.com/tlsa-hash
{"hash_311": "a30dc1ad...", "hash_301": "05754994...", "cert_path": "/etc/openpanel/caddy/ssl/.../example.com.crt", "type": "letsencrypt"}
DNS zone (nested under domains)​
This mirrors the standalone DNS Zone Editor below but is scoped under /api/domains/<domain>/.... Note the field name difference: record creation here uses value, while the standalone /api/dns endpoints use record.
GET /api/domains/example.com/dns
{
"domain": "example.com",
"serial": "2025011501",
"records": [
{"line_number": 12, "end_line_number": 12, "record": "www 3600 IN A 1.2.3.4"}
]
}
Save the whole zone file at once:
PUT /api/domains/example.com/dns
Content-Type: application/json
{"zone_content": "$ORIGIN example.com.\n..."}
Add a record:
POST /api/domains/example.com/dns/records
Content-Type: application/json
{"name": "www", "ttl": "3600", "type": "A", "value": "1.2.3.4"}
Update / delete a record by line number (end_row_id covers multi-line records like DKIM):
PUT /api/domains/example.com/dns/records/12
Content-Type: application/json
{"content": "www 3600 IN A 5.6.7.8", "end_row_id": 12, "serial": "2025011501"}
DELETE /api/domains/example.com/dns/records/12
Content-Type: application/json
{"end_row_id": 12}
Reset the zone to the default template, or export it as a file:
POST /api/domains/example.com/dns/restart
GET /api/domains/example.com/dns/export
Access logs​
GET /api/domains/example.com/logs?page=1&show_all=false
{
"domain": "example.com",
"logs": [{"ts": "2025-01-15T12:00:00Z", "status": 200, "path": "/"}],
"total": 1,
"page": 1,
"total_pages": 1,
"items_per_page": 1000
}
Email​
List mailboxes​
GET /api/emails
{
"emails": [
]
}
Create mailbox​
domain, username, and password are all required. Optional gb + format set a custom quota.
POST /api/emails
Content-Type: application/json
{"domain": "example.com", "username": "info", "password": "mailpass123"}
Get mailbox details​
GET /api/emails/[email protected]
{
"quota_limit": "1024",
"quota_used": "12",
"incoming": "ACCEPT",
"outgoing": "ACCEPT",
"server_ip": "203.0.113.10",
"mail_host": "mail.example.com"
}
Update mailbox​
At least one of gb+format, incoming (suspend/allow), outgoing (suspend/allow), or password is required.
PATCH /api/emails/[email protected]
Content-Type: application/json
{"password": "newpass456"}
Delete mailbox​
DELETE /api/emails/[email protected]
Download mail client configuration​
config_type is one of thunderbird, outlook, apple.
GET /api/emails/configuration/thunderbird/[email protected]?ssl=true
List aliases​
GET /api/emails/aliases
{
"aliases": [
]
}
Create alias​
POST /api/emails/aliases
Content-Type: application/json
{"username": "alias", "domain": "example.com", "target": "[email protected]"}
Get targets for one alias​
GET /api/emails/aliases/[email protected]
Add a target to an existing alias​
POST /api/emails/aliases/[email protected]
Content-Type: application/json
{"target": "[email protected]"}
Delete an alias target (or the whole alias)​
DELETE /api/emails/aliases/[email protected]
Content-Type: application/json
{"target": "[email protected]"}
Delete every target for the alias:
DELETE /api/emails/aliases/[email protected]
Content-Type: application/json
{"delete_all": true}
Get catch-all address​
GET /api/emails/default/example.com
{"domain": "example.com", "destination": null}
Set / remove catch-all address​
Omit or empty destination to remove it.
PUT /api/emails/default/example.com
Content-Type: application/json
{"destination": "[email protected]"}
Check deliverability (all domains)​
GET /api/emails/deliverability
Check deliverability for a domain​
Compares live DNS against what the mail server expects for DKIM, SPF, and DMARC.
GET /api/emails/deliverability/example.com
{
"domain": "example.com",
"server_ip": "203.0.113.10",
"ok": true,
"dkim": {"status": "ok", "current": "v=DKIM1; ...", "expected": "v=DKIM1; ..."},
"spf": {"status": "ok", "current": "v=spf1 ip4:203.0.113.10 ~all", "expected": "v=spf1 ip4:203.0.113.10 ~all"},
"dmarc": {"status": "ok", "current": "v=DMARC1; p=none", "expected": "v=DMARC1; p=none"}
}
Get Sieve mail filters​
GET /api/emails/filters/[email protected]
{
"raw": "require [\"fileinto\"];\nif header :contains \"Subject\" \"SPAM\" { fileinto \"Junk\"; }",
"parsed": [{"condition": "header :contains \"Subject\" \"SPAM\"", "action": "fileinto \"Junk\""}]
}
Update Sieve filters​
PUT /api/emails/filters/[email protected]
Content-Type: application/json
{"content": "require [\"fileinto\"];\nif header :contains \"Subject\" \"SPAM\" { fileinto \"Junk\"; }"}
Export mailboxes​
Returns a CSV download (email,password,quota — password is always blank, since it's never stored in reversible form).
GET /api/emails/export
Import mailboxes​
Two-step: the first call validates a CSV upload and returns an import_token; the second confirms it and creates the mailboxes.
POST /api/emails/import
Content-Type: multipart/form-data
{"import_token": "...", "valid_users": [{"email": "[email protected]", "..."}], "invalid_users": []}
POST /api/emails/import/confirm
Content-Type: application/json
{"import_token": "..."}
FTP​
List FTP accounts​
GET /api/ftp
{
"accounts": [{"username": "[email protected]", "path": "/var/www/html", "uid": "1000", "gid": "1000"}],
"server_ip": "203.0.113.10",
"ftp_host": "203.0.113.10"
}
Create FTP account​
domain is required — the account is created as username@domain, and path must start with /var/www/html/.
POST /api/ftp
Content-Type: application/json
{"username": "ftpuser", "password": "ftppass", "domain": "example.com", "path": "/var/www/html/"}
Delete FTP account​
DELETE /api/ftp/[email protected]
Change FTP password​
PATCH /api/ftp/[email protected]/password
Content-Type: application/json
{"password": "newftppass"}
Change FTP home path​
PATCH /api/ftp/[email protected]/path
Content-Type: application/json
{"path": "/var/www/html/subdir"}
List active FTP connections​
GET /api/ftp/connections
Download FTP client configuration​
config_type is one of cyberduck, filezilla.
GET /api/ftp/configuration/filezilla/[email protected]
Cron Jobs​
List all cron jobs​
GET /api/crons
{
"jobs": [
{"comment": "my job", "schedule": "0 * * * *", "container": "php-fpm-8.1", "command": "/usr/bin/php /var/www/html/cron.php"}
],
"containers": ["php-fpm-8.1", "nginx"],
"schedule_issues": []
}
Create a cron job​
schedule, command, and container are required. comment defaults to the container name.
POST /api/crons
Content-Type: application/json
{"schedule": "*/5 * * * *", "command": "/usr/bin/php /var/www/html/run.php", "container": "php-fpm-8.1", "comment": "my job"}
Edit a cron job​
The job is located by matching original_schedule, original_command, original_container, and original_comment exactly against the existing entry.
PATCH /api/crons
Content-Type: application/json
{
"original_schedule": "*/5 * * * *",
"original_command": "/usr/bin/php /var/www/html/run.php",
"original_container": "php-fpm-8.1",
"original_comment": "my job",
"schedule": "0 */2 * * *",
"command": "/usr/bin/php /var/www/html/run.php",
"container": "php-fpm-8.1",
"comment": "my job"
}
Delete a cron job​
schedule, command, container, and comment must all match the existing job exactly.
DELETE /api/crons
Content-Type: application/json
{"schedule": "*/5 * * * *", "command": "/usr/bin/php /var/www/html/run.php", "container": "php-fpm-8.1", "comment": "my job"}
Get / save raw crontab​
GET /api/crons/raw
PUT /api/crons/raw
Content-Type: application/json
{"content": "0 * * * * /usr/bin/php /var/www/html/cron.php\n"}
Tail cron log​
GET /api/crons/log?lines=50&job=cron.php
Cache Services​
Redis, Valkey, Memcached, Elasticsearch, and OpenSearch all follow the same two-endpoint pattern: a status GET and an action POST that takes {"action": "enable" | "disable" | "restart"}.
Redis / Valkey / Memcached / Elasticsearch / OpenSearch​
GET /api/cache/redis
POST /api/cache/redis
Content-Type: application/json
{"action": "enable"}
{
"service": "redis", "port": 6379,
"description": "In-memory key-value store used primarily as an application cache.",
"container_state": "running", "health_status": "healthy",
"actions": ["disable"]
}
Same pattern for the rest, just replace redis with the service name:
GET /api/cache/valkey
POST /api/cache/valkey {"action": "disable"}
GET /api/cache/memcached
POST /api/cache/memcached {"action": "restart"}
GET /api/cache/elasticsearch
POST /api/cache/elasticsearch {"action": "enable"}
GET /api/cache/opensearch
POST /api/cache/opensearch {"action": "enable"}
Varnish​
Varnish adds stats and per-domain toggles on top of the standard status/action pair.
# Status + domain list
GET /api/cache/varnish
{
"service": "varnish", "container_state": "stopped", "health_status": "unknown",
"actions": ["enable", "restart"],
"domain_statuses": {"example.com": "Off"}
}
# Enable/disable/restart
POST /api/cache/varnish
Content-Type: application/json
{"action": "enable"}
# Hit ratio and traffic stats
GET /api/cache/varnish/stats
# Per-domain Varnish status
GET /api/cache/varnish/domains
# Toggle Varnish for a specific domain
POST /api/cache/varnish/domains/example.com
Content-Type: application/json
{"status": "On"}
{
"status": "running",
"cache": {"hit_ratio": 0.92, "hits": 9200, "misses": 800, "pass": 0},
"traffic": {"requests_total": 10000, "connections": 500},
"backend": {"requests": 800, "failures": 0, "retries": 0, "health": "healthy"},
"memory": {"objects": 120, "evictions": 0},
"performance": {"efficiency_score": 92, "error_rate": 0.0}
}
Docker / Containers​
List compose services​
GET /api/containers
List running containers​
GET /api/containers/status
{"running_containers": ["nginx", "mysql", "php-fpm-8.2"]}
Get single container state​
GET /api/containers/nginx/status
{"service": "nginx", "state": "running", "health": "healthy"}
Start / stop / restart​
POST /api/containers/nginx/start
POST /api/containers/nginx/stop
POST /api/containers/nginx/restart
Start with an image pull first:
POST /api/containers/nginx/start
Content-Type: application/json
{"pull": true}
Update CPU/RAM limits​
PATCH /api/containers/myapp/resources
Content-Type: application/json
{"cpu": "1", "ram": "512m"}
Tail container logs​
GET /api/containers/nginx/logs?lines=200
{"service": "nginx", "lines": 200, "entries": [{"log": "2025/01/15 12:00:00 [notice] start worker process"}]}
Add / edit / remove a compose service​
Not to be confused with PATCH /api/containers/<service>/resources (CPU/RAM limits on an existing service, documented above) — these manage the service's presence and definition in docker-compose.yml itself. volumes is a list of {"name": "...", "mount": "...", "readonly": false}.
POST /api/containers
Content-Type: application/json
{
"service_name": "phpmyadmin",
"image": "phpmyadmin:latest",
"cpu": "1",
"ram": "512m",
"network": "db",
"environment": "PMA_HOST=mariadb",
"add_socket": false,
"volumes": []
}
PATCH /api/containers/myservice
Content-Type: application/json
{"cpu": "1", "ram": "512m", "environment": "..."}
DELETE /api/containers/myservice
Switch the MySQL/MariaDB variant​
Wipes the old data volume.
POST /api/containers/mysql
Content-Type: application/json
{"new_sql": "mysql:8.0"}
Switch the active webserver​
POST /api/containers/webserver
Content-Type: application/json
{"new_ws": "nginx"}
Change a service's image​
PATCH /api/containers/myservice/image
Content-Type: application/json
{"new_tag": "myimage:tag"}
WAF (Web Application Firewall)​
Status for all domains​
GET /api/waf
{"domains": {"example.com": "On"}}
Status for a domain​
GET /api/waf/example.com
{"domain": "example.com", "status": "On", "removed_rules": [], "removed_tags": []}
Toggle WAF on/off​
POST /api/waf/example.com
Content-Type: application/json
{"status": "On"}
Update excluded rules​
The body fields are removed_rules (numeric rule IDs) and removed_tags — not remove_by_id/remove_by_tag.
PUT /api/waf/example.com/rules
Content-Type: application/json
{
"removed_rules": ["920170", "941100"],
"removed_tags": ["attack-sqli"]
}
Paginated WAF log​
GET /api/waf/log/example.com?page=1&per_page=25
WAF stats​
GET /api/waf/stats/example.com?seconds=3600
{"domain": "example.com", "seconds": 3600, "checks": 120, "blocks": 4}
List rule IDs / tags​
Both calls hit the same parametrized route, /api/waf/ids/<id_type>, which only accepts ids or tags as the path segment:
GET /api/waf/ids/ids
GET /api/waf/ids/tags
{"ids": ["920170", "941100"]}
IP Blocker​
List blocked IPs​
GET /api/ip-blocker
{"blocked_ips": ["1.2.3.4", "10.0.0.0/8"]}
Block IPs​
Accepts single IPs and CIDR ranges.
POST /api/ip-blocker
Content-Type: application/json
{"ips": ["1.2.3.4", "10.0.0.0/8"]}
{"blocked": ["1.2.3.4", "10.0.0.0/8"], "invalid": [], "message": "2 IP(s) added to blocklist"}
Unblock all IPs​
DELETE /api/ip-blocker
PHP​
List installed PHP versions​
GET /api/php/versions
Get php.ini content​
GET /api/php/8.2/ini
Save php.ini​
PUT /api/php/8.2/ini
Content-Type: application/json
{"content": "memory_limit = 256M\nupload_max_filesize = 64M\n..."}
Get PHP option keys and values​
GET /api/php/8.2/options
Update specific PHP options​
PUT /api/php/8.2/options
Content-Type: application/json
{"memory_limit": "256M", "upload_max_filesize": "64M", "max_execution_time": "300"}
List extensions with state​
GET /api/php/8.2/extensions
{
"version": "8.2",
"service": "php-fpm-8.2",
"extensions": [
{"name": "imagick", "state": "active"},
{"name": "redis", "state": "disabled"},
{"name": "swoole", "state": "not_installed"}
],
"history": []
}
Toggle an extension​
enable is a boolean, not an action string. PHP-FPM is restarted afterwards.
POST /api/php/8.2/extensions
Content-Type: application/json
{"extension": "imagick", "enable": true}
List all supported extensions​
GET /api/php/8.2/extensions/available
Install extensions​
extensions is a list (not a single extension string) — installation runs asynchronously.
POST /api/php/8.2/extensions/install
Content-Type: application/json
{"extensions": ["swoole"]}
{"install_id": "b3f1...-uuid", "extensions": ["swoole"], "message": "Install started"}
Poll install status​
GET /api/php/8.2/extensions/install/status?install_id=b3f1...-uuid
Get / set the server-wide default PHP version​
Used for new sites/domains without an explicit override. Not available on LiteSpeed the same way — see the per-domain note below.
GET /api/php/default
{"version": "7.1", "service": "php-fpm-7.1", "is_litespeed": false, "installed_versions": ["8.5", "8.4", "..."]}
PUT /api/php/default
Content-Type: application/json
{"version": "8.2"}
List / change per-domain PHP version assignments​
PUT takes the domain in the request body, not the URL path. Not available on LiteSpeed, which has no per-domain PHP — use PUT /api/php/default instead.
GET /api/php/domains
{
"domains": [{"DomainID": 52, "DomainURL": "example.com", "PHPVersion": "8.5", "Level": "good"}],
"counts": [{"Version": "8.5", "Count": 7, "Label": "Supported (Latest)", "Level": "good"}],
"outdated_domains": [],
"available_php_versions": ["8.5", "8.4", "..."],
"php_default_version": "7.1",
"is_litespeed": false
}
PUT /api/php/domains
Content-Type: application/json
{"domain": "example.com", "version": "8.2"}
Account​
Get account info​
GET /api/account
{
"username": "stefan",
"plan_id": 1,
"context": "stefan",
"permit_username_change": false
}
Update account​
PATCH /api/account
Content-Type: application/json
{"email": "[email protected]"}
# or
{"password": "newpass"}
# or (only if allowed on this server)
{"username": "newname"}
List active sessions​
GET /api/account/sessions
Terminate a session​
DELETE /api/account/sessions/<token>
List / add / remove favorites​
GET /api/account/favorites
{"favorites": [{"link": "dashboard", "title": "Dashboard"}]}
POST /api/account/favorites
Content-Type: application/json
{"link": "dashboard", "title": "Dashboard"}
DELETE /api/account/favorites
Content-Type: application/json
{"link": "dashboard"}
Get / set preferred UI language​
GET /api/account/language
{"locales": ["en", "de", "fr", "..."], "current": "en"}
PUT /api/account/language
Content-Type: application/json
{"locale": "de"}
Get login history​
GET /api/account/login-history
{
"entries": [
{"ip": "203.0.113.10", "country_code": "US", "login_time": "2026-01-15 12:00:00"}
]
}
Two-factor authentication​
Setup is two calls: POST .../setup generates a pending secret (2FA still
off), then POST .../confirm validates a code against it and turns 2FA
on. Enrolling a passkey isn't available over the API — use the panel
UI at /account/passkeys to add one. Listing and revoking existing
passkeys is available below.
GET /api/account/2fa
{"enabled": false}
POST /api/account/2fa/setup
{"secret": "AHFB5DLV55SIZASCJEKFMSUPQ2GW6ZGV", "otpauth_url": "otpauth://totp/username?secret=...&issuer=OpenPanel"}
POST /api/account/2fa/confirm
Content-Type: application/json
{"otp_code": "123456"}
DELETE /api/account/2fa
List / revoke passkeys​
GET /api/account/passkeys
{"passkeys": [{"id": 1, "name": "Passkey", "created_at": "2026-01-15 12:00:00", "last_used_at": ""}]}
DELETE /api/account/passkeys/1
List / create / revoke MCP tokens​
The raw token is only ever returned once, at creation.
GET /api/account/mcp
{"tokens": [{"id": 1, "name": "my token", "token_prefix": "op_mcp_abc123", "read_only": true, "created_at": "2026-01-15 12:00:00"}]}
POST /api/account/mcp
Content-Type: application/json
{"name": "my token", "read_only": true, "expires_in_days": 30}
{"token": "op_mcp_...", "name": "my token", "read_only": true, "expires_in_days": 30}
DELETE /api/account/mcp/1
Get / update notification preferences​
PUT only changes the keys present in the body — keys you omit are left
as-is.
GET /api/account/notifications
{"preferences": [{"key": "notify_account_login", "value": true, "label": " account login"}]}
PUT /api/account/notifications
Content-Type: application/json
{"preferences": {"notify_account_login": false}}
Resource Usage​
Latest snapshot​
GET /api/usage
{
"cpu": {"usage": {"pct": 12.5}},
"memory": {"usage_pct": 45, "used": {"human": "900MB"}, "total": {"human": "2GB"}},
"bandwidth": {"usage_pct": 5, "limit": {"human": "100GB"}}
}
Paginated history​
GET /api/usage/history?page=1&per_page=25
Hosting Info​
Read-only informational endpoints, always available.
General hosting info​
GET /api/hosting/info
{"system": "Linux", "node": "openpanel", "release": "5.14.0-...", "version": "...", "machine": "x86_64", "processor": "unknown", "ip": "203.0.113.10", "uptime": "11 days", "load_avg": "2.37, 2.72, 2.75"}
Hosting plan details​
GET /api/hosting/plan
{
"context": "myuser", "plan_webserver": "nginx", "plan_mysql": "mariadb",
"plan_cpu_limit": "4", "plan_ram_limit": "6g", "plan_disk_limit": "50 GB",
"plan_bandwidth": "500", "plan_domains_limit": "10", "plan_websites_limit": "10",
"plan_db_limit": "20", "plan_email_limit": "500", "plan_ftp_limit": "100",
"plan_inodes_limit": "1000000", "plan_max_email_quota": "2G",
"plan_description": "A professional plan", "ns1": "", "ns2": "", "ns3": "", "ns4": ""
}
Allocated remote-access ports​
GET /api/hosting/ports
{"remote_mysql_port": "32770", "remote_postgres_port": "32771"}
Disk Usage & Inodes​
Disk usage (root)​
GET /api/disk-usage
Disk usage for a path​
GET /api/disk-usage/public_html
{
"directory": "public_html",
"entries": [
{"size": "120M", "path": "wp-content"},
{"size": "3.2M", "path": "wp-includes"}
]
}
Inode count (root)​
GET /api/inodes
Inode count for a path​
GET /api/inodes/public_html
{
"directory": "public_html",
"entries": [{"folder": "wp-content", "inode_count": 4200}]
}
Malware Scanner​
List quarantined files​
GET /api/malware-scanner/quarantine
{"quarantine_files": [], "count": 0}
Run a scan​
POST /api/malware-scanner/scan
Content-Type: application/json
{"directory": "/var/www/html"}
{
"directory": "/var/www/html",
"infected_files": [],
"infected_count": 0,
"summary": ["Scanned files: 1234", "Infected files: 0"],
"return_code": 0
}
Backup Wizard​
The single-click "back up my whole account" flow — creates a .tar.gz of the account's docroot volume in the background.
Get backup status and existing backups​
GET /api/backup-wizard/status
{
"in_progress": false,
"in_progress_started": "2026-01-15 12:00:00",
"in_progress_size": "45.2 MB",
"backups": [
{"name": "myuser_backup_2026-01-15_12-00-00.tar.gz", "size_raw": 47448064, "size": "45.2 MB", "mtime": "2026-01-15 12:00:00", "in_progress": false}
]
}
in_progress_started and in_progress_size are only present while a backup is running.
Start a backup​
Fire-and-forget — returns immediately, poll GET /api/backup-wizard/status for progress. Returns 409 if a backup is already running.
POST /api/backup-wizard/create
{"message": "Backup started. Poll GET /api/backup-wizard/status for progress."}
Download a backup​
Streams the .tar.gz. Returns 409 if the requested file is still the one currently being written.
GET /api/backup-wizard/download/myuser_backup_2026-01-15_12-00-00.tar.gz
File Manager​
All paths are relative to /var/www/html/. A path that would escape the account's docroot is always rejected, regardless of how it's phrased.
List a directory​
GET /api/files/subdir?hidden_files=true&page=1
{
"files_info": [{"Permissions": "-rw-r--r--", "Links": "1", "Owner": "1001", "Group": "1001", "Size": "0", "Date": "Aug 10 20:45", "Name": "hello.txt", "LinkTarget": "", "Type": "file"}],
"pagination": {"current_page": 1, "total_pages": 1, "per_page": 500, "total_files": 1},
"limits": {"edit_size_mb": 5, "view_size_mb": 5, "download_size_mb": 2000, "upload_size_mb": 2000},
"extensions": {"extensions": ".txt .md .php ...", "images": ".jpg .png ...", "archives": ".zip .tar.gz ..."}
}
List subfolders (for a copy/move destination picker)​
GET /api/folders/subdir
{"folders": [{"name": "images", "path": "subdir/images", "has_subfolders": false}]}
Create a file / directory​
POST /api/file-manager/new-file
Content-Type: application/json
{"path": "subdir", "filename": "notes.txt"}
POST /api/file-manager/new-directory
Content-Type: application/json
{"path": "subdir", "foldername": "images"}
Upload files​
Multipart — field name must be files (repeatable), plus a path_param field for the destination directory.
POST /api/file-manager/upload
Content-Type: multipart/form-data
path_param=subdir
{"uploaded": ["photo.jpg"], "errors": []}
Rename​
POST /api/file-manager/rename
Content-Type: application/json
{"path": "subdir", "old_name": "old.txt", "new_name": "new.txt"}
Delete​
mode is permanent (default) or trash.
DELETE /api/file-manager/delete?filename=old.txt&path_param=subdir&item_type=file&mode=trash
Change permissions​
Applies one octal mode to one or more files at once.
POST /api/file-manager/permissions
Content-Type: application/json
{"path": "subdir", "filenames": ["a.txt", "b.txt"], "permissions": "644"}
{"changed": ["a.txt", "b.txt"], "errored": []}
Copy / move​
POST /api/file-manager/copy?item_name=a.txt&item_type=file&path_param=subdir&destination_path=other/a.txt
POST /api/file-manager/move?item_name=a.txt&path_param=subdir&destination_path=other/a.txt
Get / save file content​
GET returns the raw content as a bare JSON string (not wrapped in an object). Both are limited to editable text file types; PUT also normalizes line endings for known text file types.
GET /api/file-manager/edit-file/subdir/notes.txt
"file content here"
PUT /api/file-manager/edit-file/subdir/notes.txt
Content-Type: application/json
{"content": "new file content"}
Download / view a file​
Download streams application/octet-stream with a Content-Disposition attachment header; view returns the raw content as text/plain (or serves the file directly for images). Both take path_param as a query parameter for the parent directory.
GET /api/file-manager/download-file/subdir/notes.txt
GET /api/file-manager/view-file/subdir/notes.txt
Extract / create an archive​
Supports zip, tar, tar.gz/tgz, and single-file gzip. extract_destination defaults to the archive's own directory; omit or use / for the docroot itself.
POST /api/file-manager/extract-archive
Content-Type: application/json
{"archive_name": "backup.zip", "path": "subdir", "extract_destination": "subdir/extracted"}
POST /api/file-manager/create-archive
Content-Type: application/json
{"archive_name": "backup", "extension": "zip", "path": "subdir", "selected_files": ["a.txt", "b.txt"]}
Download a file from a URL​
Fire-and-forget. Only public http/https URLs are allowed — loopback, private, and link-local addresses are rejected. Returns a download_id to poll.
POST /api/file-manager/wget
Content-Type: multipart/form-data
url=https://example.com/file.zip
path_param=subdir
GET /api/file-manager/wget/status/<download_id>
{"progress": 100, "status": "done", "message": "File downloaded from URL successfully to /var/www/html/subdir"}
Trash​
List trashed items​
GET /api/trash
[{"Permissions": "-rw-r--r--", "Name": "old.txt", "Type": "file", "DeletionDate": "2026-01-15T12:00:00", "OriginalPath": "/var/www/html/subdir/old.txt"}]
Restore / permanently delete one item​
POST /api/trash/restore?filename=old.txt
DELETE /api/trash/delete?filename=old.txt
Restore / delete everything​
POST /api/trash/restore-all
{"restored": ["old.txt"], "errors": []}
POST /api/trash/delete-all
Webserver Config​
Get config​
GET /api/webserver-conf
{
"web_server": "nginx",
"label": "Nginx",
"filename": "nginx.conf",
"service": "nginx",
"content": "server { ... }"
}
Save config​
Validated and restarted automatically; the previous content is restored if the syntax check fails.
PUT /api/webserver-conf
Content-Type: application/json
{"content": "server {\n listen 80;\n ...\n}"}
Process Manager​
List running processes​
GET /api/process-manager
{
"processes": [
{"container": "nginx", "uid": "0", "pid": "1234", "ppid": "1", "cpu": "0.0", "stime": "12:00", "tty": "?", "time": "00:00:01", "cmd": "nginx: master process"}
],
"count": 1
}
Kill a process​
DELETE /api/process-manager/1234
Fix Permissions​
List directories​
GET /api/fix-permissions
{"directories": ["/var/www/html/example.com", "/var/www/html/example.com/wp-content"]}
Fix permissions​
Omit directory (or pass /) to fix the whole account; otherwise it must resolve inside /var/www/html/.
POST /api/fix-permissions
Content-Type: application/json
{"directory": "/var/www/html/example.com"}
Sites & Websites​
List all websites​
GET /api/sites
Get website details​
GET /api/sites/example.com
Google Safe Browsing check​
GET /api/sites/example.com/safebrowsing
{"status": "safe", "url": "http://example.com", "threats": []}
Get / trigger PageSpeed data​
GET /api/sites/example.com/pagespeed
POST /api/sites/example.com/pagespeed
Get / run WP vulnerability scan​
GET /api/sites/example.com/wp-vulnerability
POST /api/sites/example.com/wp-vulnerability
Generate a temporary preview link​
GET /api/sites/example.com/temporary-link
Get recent visitors​
GET /api/sites/example.com/visitors?seconds=60
{"domain": "example.com", "seconds": 60, "count": 3, "ips": ["203.0.113.10", "..."]}
Get WordPress site info​
PHP/MySQL/WP versions and database credentials for a WordPress site. Supports subfolder sites (e.g. example.com/blog).
GET /api/sites/example.com/wp-info
{
"wp_version": "6.7.1",
"php_version": "8.2",
"mysql_version": "10.11.6",
"database_info": {"database_host": "mariadb", "database_name": "wp_db", "database_user": "wp_user", "database_password": "...", "database_table_prefix": "wp_"}
}
WordPress​
List all WordPress installations​
GET /api/wordpress
List backups​
GET /api/wordpress/example.com/backups
{
"domain": "example.com",
"backups": [
{"date": "2025-01-15_12-00-00", "hasDbBackup": true, "hasFilesBackup": true}
]
}
Create backup​
POST /api/wordpress/example.com/backups
Content-Type: application/json
{"backup_database": true, "backup_files": true}
{"message": "Backup completed successfully", "timestamp": "2025-01-15_12-00-00"}
Restore a backup​
POST /api/wordpress/example.com/restore
Content-Type: application/json
{"backup_date": "2025-01-15_12-00-00"}
{"message": "Backup restored: files and database", "restored": ["files", "database"]}
List available hardening rules​
GET /api/wordpress/secure
{"rules": ["wp_manager_xmlrpc", "wp_manager_user_enum", "wp_manager_wlwmanifest"]}
Get active hardening rules​
GET /api/wordpress/example.com/secure
Apply hardening rules​
PUT /api/wordpress/example.com/secure
Content-Type: application/json
{"rules": ["wp_manager_xmlrpc", "wp_manager_wlwmanifest"], "disable_all": false}
Disable all rules:
PUT /api/wordpress/example.com/secure
Content-Type: application/json
{"disable_all": true}
Uninstall WordPress​
Removes files and drops the database. Note the path is /api/wordpress/sites/<id> (the numeric sites.id, not the domain).
DELETE /api/wordpress/sites/42
Detach from WordPress Manager​
Removes from manager, keeps files.
POST /api/wordpress/sites/42/detach
Reload WP data from filesystem​
Rescans /var/www/html for wp-config.php files and refreshes site metadata.
POST /api/wordpress/reload
WP-CLI actions​
Available actions: core_update, core_update_check, plugin_update_all, theme_update_all, list_plugins, list_themes, cache_flush, cron_run.
POST /api/wp-cli/plugin_update_all
Content-Type: application/json
{"domain": "example.com"}
{
"action": "plugin_update_all",
"domain": "example.com",
"stdout": "Success: Updated 3 of 3 plugins.",
"stderr": "",
"returncode": 0
}
Node.js / Python Apps (PM2)​
Tail app logs​
GET /api/pm2/example.com/logs?lines=100
Start / stop / restart application​
Restart pulls the latest image first.
POST /api/pm2/example.com/start
POST /api/pm2/example.com/stop
POST /api/pm2/example.com/restart
Update app settings​
Any of version, requirements, startup_file, custom_cmd, workdir, cpu, ram may be sent.
PATCH /api/pm2/example.com
Content-Type: application/json
{
"version": "20",
"startup_file": "/var/www/html/index.js",
"cpu": "1",
"ram": "1"
}
Delete app​
Stops the container, reverts the webserver config, removes the service from docker-compose.yml, and deletes it from the database.
DELETE /api/pm2/example.com
Install a new app​
Creates the app (the endpoints above then manage it). The request body is form-encoded (not JSON), and the response streams newline-delimited JSON status/error events as the install progresses, rather than a single JSON object.
domain_id and service_name are required. Optional: startup_file, cpu_limit, mem_limit, port, subdirectory, version (defaults to latest), custom_cmd, requirements, git_repo_url.
POST /api/nodejs/install
Content-Type: application/x-www-form-urlencoded
domain_id=12&service_name=myapp&version=20&startup_file=index.js
POST /api/python/install
Content-Type: application/x-www-form-urlencoded
domain_id=12&service_name=myapp&version=3.12&startup_file=app.py
{"status": "Checking if existing installation processes are running.."}
{"status": "Validating provided data"}
{"status": "Service added successfully", "success": true}
{"status": "Starting docker container.."}
Helpers​
Small utility endpoints backing the Node.js/Python install forms.
Get available runtime versions​
type is nodejs or python. Proxies endoflife.date's release feed, cached 24h.
GET /api/docker/tags/nodejs
Check whether a file exists in the app's docroot​
Only .py and .js files are checked.
POST /api/helpers/check-file-exists
Content-Type: application/json
{"file": "app.py"}
{"file": "app.py", "exists": true}
Detect a Git repo's startup file​
Clones the repository to detect its entry point (package.json's main, or common filenames like index.js/app.py).
POST /api/helpers/detect-git-startup-file
Content-Type: application/json
{"git_repo_url": "https://github.com/user/repo.git", "app_type": "nodejs"}
{"startup_file": "index.js"}
Auto Installer​
List installed apps​
GET /api/autoinstaller
{
"sites": [{"site_name": "example.com", "type": "WordPress"}],
"counts": {"wordpress": 1, "drupal": 0, "sitebuilder": 0, "node": 0, "python": 0, "java": 0, "ruby": 0, "bun": 0, "mautic": 0, "flarum": 0, "fossbilling": 0},
"technologies": ["wordpress", "drupal", "sitebuilder", "node", "python", "java", "ruby", "bun", "mautic", "flarum", "fossbilling"],
"domain_count": 1
}
Dynamic DNS​
List all entries​
GET /api/dynamic-dns
{
"domains": {
"example.com": [
{"line_number": 14, "subdomain": "home", "ttl": "300", "type": "A", "record": "1.2.3.4", "token": "abc123xyz", "last_updated": "2025-01-15T12:00:00Z", "raw_line": "home 300 IN A 1.2.3.4 ; webcall=abc123xyz updated=2025-01-15T12:00:00Z"}
]
}
}
Create entry​
POST /api/dynamic-dns
Content-Type: application/json
{"domain": "example.com", "subdomain": "home", "ip": "0.0.0.0"}
{"message": "Dynamic DNS entry created", "entry": {"subdomain": "home", "record": "0.0.0.0", "type": "A", "token": "abc123xyz"}}
Update entry​
domain, line_number, subdomain, ip, and token are all required.
PUT /api/dynamic-dns
Content-Type: application/json
{
"domain": "example.com",
"line_number": 14,
"subdomain": "home",
"ip": "1.2.3.4",
"token": "abc123xyz"
}
Delete entry​
DELETE /api/dynamic-dns
Content-Type: application/json
{"domain": "example.com", "line_number": 14}
Auto-update IP (no auth required)​
Called by your router or DDNS client to update the record with the caller's IP:
GET /dynamic-dns/update?token=abc123xyz
DNS Zone Editor​
A standalone zone editor at /api/dns/..., separate from the domain-scoped DNS endpoints under /api/domains/<domain>/dns.... The two overlap in purpose but use different field names — this one's parsed records use line/multiline (not record), and new records use the field record (not value).
List domains with zone status​
GET /api/dns
{"domains": [{"domain_id": 1, "domain_url": "example.com", "zone_file_exists": true}]}
Get parsed DNS records​
GET /api/dns/example.com
{
"domain": "example.com",
"serial": "2025011501",
"records": [
{"line_number": 12, "end_line_number": 12, "line": "www 3600 IN A 1.2.3.4", "multiline": false}
],
"validation_error": null
}
Get raw zone file​
GET /api/dns/example.com/raw
Save raw zone file​
PUT /api/dns/example.com/raw
Content-Type: application/json
{"content": "$ORIGIN example.com.\n$TTL 3600\n..."}
Add a DNS record​
POST /api/dns/example.com/records
Content-Type: application/json
{"name": "www", "ttl": "3600", "type": "A", "record": "1.2.3.4"}
MX example:
{"name": "example.com.", "ttl": "3600", "type": "MX", "priority": "10", "record": "mail.example.com."}
TXT / SPF example:
{"name": "example.com.", "ttl": "3600", "type": "TXT", "record": "v=spf1 include:_spf.example.com ~all"}
Update a record by line number​
PATCH /api/dns/example.com/records/12
Content-Type: application/json
{"content": "www 3600 IN A 5.6.7.8", "serial": "2025011501"}
The serial field prevents concurrent-edit conflicts — pass the serial you read when fetching the zone.
Delete a record​
DELETE /api/dns/example.com/records/12
Multi-line record (e.g. DKIM):
DELETE /api/dns/example.com/records/15
Content-Type: application/json
{"end_row_id": 18}
Reset zone to default template​
POST /api/dns/example.com/reset
Export zone file​
Unlike the domain-scoped export (which streams a file download), this returns the zone content as JSON.
GET /api/dns/example.com/export
{"domain": "example.com", "content": "$ORIGIN example.com.\n...", "filename": "example.com.zone"}
Traffic Stats (GoAccess)​
List domains with stats availability​
GET /api/stats
{"domains": [{"domain_url": "example.com", "has_stats": true}]}
Get GoAccess report for a domain​
GET /api/stats/example.com
{
"domain": "example.com",
"available": true,
"html": "<!DOCTYPE html>..."
}
Stats are generated every 24 hours by the GoAccess daemon.
System Services​
List all services​
GET /api/services
{
"services": [
{"service": "nginx", "container_state": "running", "health_status": "healthy"},
{"service": "mysql", "container_state": "running", "health_status": "healthy"}
]
}
Get service status​
GET /api/services/nginx
{
"service": "nginx",
"container_state": "running",
"health_status": "healthy",
"available_actions": ["disable"]
}
Start / stop / restart a service​
POST /api/services/nginx
Content-Type: application/json
{"action": "enable"}
{"message": "Service nginx enabled successfully", "service": "nginx", "container_state": "running", "health_status": "healthy"}
Webmail​
Get webmail info​
GET /api/webmail
{"is_running": true, "webmail_url": "https://webmail.example.com/"}
Generate auto-login token​
Creates a single-use token for automatic Roundcube login.
POST /api/webmail/[email protected]
{
"token": "abc123...",
"autologin_url": "https://webmail.example.com/autologin.php?token=abc123...",
"webmail_url": "https://webmail.example.com/"
}
Website Builder​
Get HTML and CSS content​
GET /api/website-builder/example.com
{
"domain": "example.com",
"docroot": "/var/www/html",
"html": "<!DOCTYPE html>...",
"css": "* { box-sizing: border-box; }"
}
Save content​
PUT /api/website-builder/example.com
Content-Type: application/json
{
"html": "<!DOCTYPE html><html>...</html>",
"css": "body { margin: 0; }"
}
Create a new site​
POST /api/website-builder
Content-Type: application/json
{"domain_id": 1, "subdirectory": ""}
{"message": "Website created successfully on example.com", "site_name": "example.com"}
Remove a site (deletes files)​
DELETE /api/website-builder/sites/7
Detach a site (keeps files)​
POST /api/website-builder/sites/7/detach
Dashboard​
Resource usage widget​
Same data the dashboard's live usage widget polls.
GET /api/dashboard/resource-usage
{
"cpu": {"usage": {"pct": 26, "human": "0.3 cores"}, "total": {"pct": 800, "human": "8.0 cores"}},
"memory": {"usage_pct": 11, "used": {"human": "723.5M"}, "total": {"human": "6.0G"}},
"bandwidth": {"usage_pct": 0, "total_sent": {"human": "0bit"}},
"tasks": {"current": 220, "limit": 900, "usage_pct": 24},
"uid": 1001, "user": "myuser", "timestamp": "2026-01-15T12:00:00Z"
}
Disk / inode usage widget​
GET /api/dashboard/disk-inodes
{"inodes_used": 87581, "inodes_soft": 1000000, "inodes_hard": 1000000, "disk_used": 2838644, "disk_soft": 51200000, "disk_hard": 51200000, "device": "/home/myuser/", "date": "2026-01-15T12:00:00Z"}
Plugins​
Only present if at least one plugin is installed on the server (GET /api/endpoints won't list it otherwise).
List installed plugins​
GET /api/plugins
{"plugins": [{"name": "example-plugin", "..."}]}
Error Responses​
All endpoints return standard HTTP status codes:
| Code | Meaning |
|---|---|
200 | OK |
201 | Created |
202 | Accepted (async operation started) |
400 | Bad request / missing required field |
401 | Unauthorized (missing or invalid token) |
403 | Forbidden (feature not enabled or domain not owned) |
404 | Resource not found |
409 | Conflict (e.g. serial mismatch, resource already exists) |
500 | Internal server error |
503 | Service unavailable |
504 | Timeout |
Error responses always include an error field:
{"error": "You do not own this domain"}
- Authentication
- MySQL
- List databases
- Create a database
- Drop a database
- List tables in a database
- Optimize / repair a database
- Export a database
- Import a database
- Get per-database disk usage
- Set the MySQL root password
- List database users
- Create a database user
- Delete a database user
- Change a database user's password
- Get a user's privileges on a database
- Grant privileges
- Revoke privileges
- Get databases/users/assignments summary
- Show full processlist
- Remote access status
- Enable / disable remote access
- Grant a remote-access entry
- Change a remote-access entry's host
- Remove a remote-access entry
- Get server configuration
- Update server configuration
- PostgreSQL
- List databases
- Create a database
- Drop a database
- Export a database
- Import a database
- List users
- Create a user
- Drop a user
- Change a user's password
- Grant all privileges on a database
- Revoke privileges
- Get databases/users/assignments summary
- Show active connections
- Remote access status
- Enable / disable remote access
- Get / update server configuration
- Domains
- List all domains
- Add a domain
- Remove a domain
- Get domain status
- Suspend / unsuspend a domain
- Get / change the document root
- Get / set / remove a redirect
- Get SSL status
- Issue / renew SSL
- Get / save the VHost config
- Get / set a display-case override
- Get the TLSA record hash
- DNS zone (nested under domains)
- Access logs
- List mailboxes
- Create mailbox
- Get mailbox details
- Update mailbox
- Delete mailbox
- Download mail client configuration
- List aliases
- Create alias
- Get targets for one alias
- Add a target to an existing alias
- Delete an alias target (or the whole alias)
- Get catch-all address
- Set / remove catch-all address
- Check deliverability (all domains)
- Check deliverability for a domain
- Get Sieve mail filters
- Update Sieve filters
- Export mailboxes
- Import mailboxes
- FTP
- List FTP accounts
- Create FTP account
- Delete FTP account
- Change FTP password
- Change FTP home path
- List active FTP connections
- Download FTP client configuration
- Cron Jobs
- List all cron jobs
- Create a cron job
- Edit a cron job
- Delete a cron job
- Get / save raw crontab
- Tail cron log
- Cache Services
- Redis / Valkey / Memcached / Elasticsearch / OpenSearch
- Varnish
- Docker / Containers
- List compose services
- List running containers
- Get single container state
- Start / stop / restart
- Update CPU/RAM limits
- Tail container logs
- Add / edit / remove a compose service
- Switch the MySQL/MariaDB variant
- Switch the active webserver
- Change a service's image
- WAF (Web Application Firewall)
- Status for all domains
- Status for a domain
- Toggle WAF on/off
- Update excluded rules
- Paginated WAF log
- WAF stats
- List rule IDs / tags
- IP Blocker
- List blocked IPs
- Block IPs
- Unblock all IPs
- PHP
- List installed PHP versions
- Get php.ini content
- Save php.ini
- Get PHP option keys and values
- Update specific PHP options
- List extensions with state
- Toggle an extension
- List all supported extensions
- Install extensions
- Poll install status
- Get / set the server-wide default PHP version
- List / change per-domain PHP version assignments
- Account
- Get account info
- Update account
- List active sessions
- Terminate a session
- List / add / remove favorites
- Get / set preferred UI language
- Get login history
- Two-factor authentication
- List / revoke passkeys
- List / create / revoke MCP tokens
- Get / update notification preferences
- Resource Usage
- Latest snapshot
- Paginated history
- Hosting Info
- General hosting info
- Hosting plan details
- Allocated remote-access ports
- Disk Usage & Inodes
- Disk usage (root)
- Disk usage for a path
- Inode count (root)
- Inode count for a path
- Malware Scanner
- List quarantined files
- Run a scan
- Backup Wizard
- Get backup status and existing backups
- Start a backup
- Download a backup
- File Manager
- List a directory
- List subfolders (for a copy/move destination picker)
- Create a file / directory
- Upload files
- Rename
- Delete
- Change permissions
- Copy / move
- Get / save file content
- Download / view a file
- Extract / create an archive
- Download a file from a URL
- Trash
- List trashed items
- Restore / permanently delete one item
- Restore / delete everything
- Webserver Config
- Get config
- Save config
- Process Manager
- List running processes
- Kill a process
- Fix Permissions
- List directories
- Fix permissions
- Sites & Websites
- List all websites
- Get website details
- Google Safe Browsing check
- Get / trigger PageSpeed data
- Get / run WP vulnerability scan
- Generate a temporary preview link
- Get recent visitors
- Get WordPress site info
- WordPress
- List all WordPress installations
- List backups
- Create backup
- Restore a backup
- List available hardening rules
- Get active hardening rules
- Apply hardening rules
- Uninstall WordPress
- Detach from WordPress Manager
- Reload WP data from filesystem
- WP-CLI actions
- Node.js / Python Apps (PM2)
- Tail app logs
- Start / stop / restart application
- Update app settings
- Delete app
- Install a new app
- Helpers
- Get available runtime versions
- Check whether a file exists in the app's docroot
- Detect a Git repo's startup file
- Auto Installer
- List installed apps
- Dynamic DNS
- List all entries
- Create entry
- Update entry
- Delete entry
- Auto-update IP (no auth required)
- DNS Zone Editor
- List domains with zone status
- Get parsed DNS records
- Get raw zone file
- Save raw zone file
- Add a DNS record
- Update a record by line number
- Delete a record
- Reset zone to default template
- Export zone file
- Traffic Stats (GoAccess)
- List domains with stats availability
- Get GoAccess report for a domain
- System Services
- List all services
- Get service status
- Start / stop / restart a service
- Webmail
- Get webmail info
- Generate auto-login token
- Website Builder
- Get HTML and CSS content
- Save content
- Create a new site
- Remove a site (deletes files)
- Detach a site (keeps files)
- Dashboard
- Resource usage widget
- Disk / inode usage widget
- Plugins
- List installed plugins
- Error Responses